Skip to content

Editing geographic points with a Scriptcase map control

This 2021 development note records a map control I built for a Scriptcase application. The task was to edit the location of a record on a map instead of relying only on typed longitude and latitude values.

The editing flow

The control opened with the record’s existing coordinates. Moving a marker updated the coordinate fields; changing the fields moved the marker. A reset action returned to the starting position, while confirmation passed the selected coordinates back to the parent form.

The stored longitude and latitude used decimal degrees in WGS 84. In the projects described here, database spatial queries also associated a point with geographic areas such as an island or administrative district.

Video record

Original implementation

The following SQL, HTML and JavaScript are retained from the original article. They depend on its table names, form fields, Scriptcase template and map integration. They are a development record, not a tested current installation package.

Database trigger example

BEGIN	
	IF  (st_x(NEW.shape)<>st_x(OLD.shape)) OR (st_y(NEW.shape)<>st_y(OLD.shape)) THEN		
		NEW.periferia_id = (SELECT periferies_new.objectid
		  from sde.periferies_new, sde.bats_locations
		  where st_contains (periferies_new.shape, bats_locations.shape) = 't'
		  and bats_locations.objectid = NEW.objectid);
		NEW.nomos_id = (SELECT nomoi.objectid
		  from sde.nomoi, sde.bats_locations
		  where st_contains (nomoi.shape, bats_locations.shape) = 't'
		  and bats_locations.objectid = NEW.objectid);
		NEW.island_id = (SELECT coast_withnames.objectid
		  from sde.coast_withnames, sde.bats_locations
		  where st_contains (coast_withnames.shape, bats_locations.shape) = 't'
		  and bats_locations.objectid = NEW.objectid);
	END IF;
	    NEW.update_date = now();
	RETURN NEW;
END

Control template

<html>
<head>
	<!--SC_PAGE_CHARSET-->
	<!--SC_JS_LIB-->
	<link rel="stylesheet" type="text/css" href="{SC_LIB_CURRENT}/css/bootstrap/bootstrap.min.css" />	
	<link rel="stylesheet" type="text/css" href="{SC_LIB_CURRENT}/css/controlpoints.css" />	
	<script type="text/javascript" src="{SC_LIB_CURRENT}/js/bootstrap/bootstrap.min.js"></script>
	<script type="text/javascript" src="{SC_LIB_CURRENT}/js/controlpoints.js"></script>
	<title><!--SC_PAGE_TITLE--></title>	
</head>
<body>
<div class="row">
	<div class="col-12 col-sm-6 col-md-8">
		<div id="map">
			<div style="width: 100%; height: 100%; " id="map"></div>
			<div id="content-window">&nbsp;</div>
		</div>	  
	</div>
  <div class="col-6 col-md-4">
		<form {SC_FORM_ATTR}>
			<!--SC_FORM_HIDDEN-->
			<!--SC_FIELD_LABEL_my_field-->
			<br />
				<input {SC_FIELD_INFO_idlocality} class="form-control {SC_FIELD_CLASS}" type="text" />
				<div class="coords">
					<div class="coord">
						<span class="label">Longitude</span>
						<input {SC_FIELD_INFO_londd} class="form-control {SC_FIELD_CLASS}" type="text" />
					</div>
					<div class="coord">
						<span class="label">Latitude</span>
						<input {SC_FIELD_INFO_latdd} class="form-control {SC_FIELD_CLASS}" type="text" />						
					</div>
					<div class="coord submitbutton">
						<input type="submit" {SC_FORM_SUBMIT_INFO} />
					</div>
				</div>
		</form>
	  	<button onclick="resetPoint()">Reset Position</button>  
		<button id="closewindow">Cancel / Exit</button>
	  	<div id="location-info">
		  	<h2>Existing location information</h2>
			<div class="location-info idlocation">
				<span class="label-info">GIS ID</span>
	  			<input {SC_FIELD_INFO_gisid} class="form-control {SC_FIELD_CLASS}" type="text" readonly="readonly" />						
			</div>
			<div class="location-info">
				<span class="label-info">District</span>
				<input {SC_FIELD_INFO_periferia} class="form-control {SC_FIELD_CLASS}" type="text" readonly="readonly" />						
			</div>
			<div class="location-info">
				<span class="label-info">Perfecture</span>				
				<input {SC_FIELD_INFO_nomos} class="form-control {SC_FIELD_CLASS}" type="text" readonly="readonly" />						
			</div>
			<div class="location-info">
				<span class="label-info">Island name</span>
				<input {SC_FIELD_INFO_island_name} class="form-control {SC_FIELD_CLASS}" type="text" readonly="readonly" />						
			</div>
			<div class="location-info">
				<span class="label-info">Area</span>
				<input {SC_FIELD_INFO_area} class="form-control {SC_FIELD_CLASS}" type="text" readonly="readonly" />						
			</div>
			<div class="location-info">
				<span class="label-info">Location name</span>
				<input {SC_FIELD_INFO_location} class="form-control {SC_FIELD_CLASS}" type="text" readonly="readonly" />						
			</div>
			<div class="location-info">
				<span class="label-info">Update date / time</span>
				<input {SC_FIELD_INFO_update_date} class="form-control {SC_FIELD_CLASS}" type="text" readonly="readonly" />									
			</div>
		</div>
	</div>
</div>	
	<!-- This contains the hidden content for inline calls -->
  <script async defer
		src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_HERE&callback=initMap">
		</script>
</body>
</html>

Map-control script

var marker;
// Initial coordinates from the parent form
var thelon;
var thelat;
// New coordinates given from the map or disrectly from the input coordinate fields
var newlon;
var newlat;
window.map = undefined;      // global variable
function scDisplayUserError(errorMessage) {
	alert("ERRORrn" + errorMessage.replace(/<br />/gi, "n"));
}
function scDisplayUserDebug(debugMessage) {
	alert("DEBUGrn" + debugMessage.replace(/<br />/gi, "n"));
}function scDisplayUserMessage(userMessage) {
	alert("MESSAGErn" + userMessage.replace(/<br />/gi, "n"));
}
// This will reset the coordinates to the initial values. This is an alternative to reexamine the initial position of the point without closing and reopening the control
function resetPoint() {
	// Set the values of the coordinates from the parent form
	thelon=parseFloat(parent.document.getElementById("id_sc_field_londd_manual").value);
	thelat=parseFloat(parent.document.getElementById("id_sc_field_latdd_manual").value);
	// Assign the values to the respective input fields of the iframe control form
    document.getElementById("id_sc_field_londd").value=thelon;
	document.getElementById("id_sc_field_latdd").value=thelat;
	// Reset the center of the map and the marker position
  	const center = new google.maps.LatLng(thelat, thelon);  
  	window.map.panTo(center);
  	marker.setPosition( new google.maps.LatLng( thelat, thelon ) );
}
function initMap() {
  // Set initial coordinates values from parent form
  thelon = parseFloat(parent.document.getElementById("id_sc_field_londd_manual").value);
  thelat = parseFloat(parent.document.getElementById("id_sc_field_latdd_manual").value);
  document.getElementById('id_sc_field_londd').value = thelon;
  document.getElementById('id_sc_field_latdd').value = thelat;	    
  var myLatLng = {lat: thelat, lng: thelon};
	// Starting parameters for the map by setting a default zoom and centering at the location of the marker	
  window.map = new google.maps.Map(document.getElementById('map'), {
    zoom: 11,
    center: myLatLng
  });
  pointLatLng = new google.maps.LatLng({lat: thelat, lng: thelon}); 
  marker = new google.maps.Marker({
    position: myLatLng,
    map: map,
    position: pointLatLng,
    draggable: true,
    title: 'Custom point'
  });
  <!-- Move the marker by dragging it -->
  marker.addListener('dragend', function(evt) {
    console.log('Lat: ' + evt.latLng.lat() + ' Lon: ' + evt.latLng.lng());
    newlon=parseFloat((Math.round(evt.latLng.lng() * 10000000) / 10000000).toFixed(7));
    newlat=parseFloat((Math.round(evt.latLng.lat() * 10000000) / 10000000).toFixed(7));
    document.getElementById('id_sc_field_londd').value = newlon;
    document.getElementById('id_sc_field_latdd').value = newlat;
  });
  <!-- On single click move the marker -->
  google.maps.event.addListener(map, 'click', function(evt) {       
    newlon=parseFloat((Math.round(evt.latLng.lng() * 10000000) / 10000000).toFixed(7));
    newlat=parseFloat((Math.round(evt.latLng.lat() * 10000000) / 10000000).toFixed(7));
	const center = new google.maps.LatLng(newlat, newlon);  
  	window.map.panTo(center);
  	marker.setPosition( new google.maps.LatLng( newlat, newlon ) );	
    document.getElementById('id_sc_field_londd').value = newlon;
    document.getElementById('id_sc_field_latdd').value = newlat;	
  });
}
$(document).ready(function () {
  $('#closewindow').click(function (){          
	$('#TB_overlay', window.parent.document).remove();    
    $('#TB_window', window.parent.document).remove();      
  });  
});
function exitModal() {		
	parent.document.getElementById('id_sc_field_londd_manual').value = newlon;
    parent.document.getElementById('id_sc_field_latdd_manual').value = newlat;
  	$('#TB_overlay', window.parent.document).remove();    
    $('#TB_window', window.parent.document).remove();      
}
// Ajax executed function on coordinate fields change
function coordinatesChanged(thelon,thelat) {	
	newlon=thelon;
	newlat=thelat;
    document.getElementById("id_sc_field_londd").value=thelon;
	document.getElementById("id_sc_field_latdd").value=thelat;
  	const center = new google.maps.LatLng(thelat, thelon);  
  	window.map.panTo(center);
  	marker.setPosition( new google.maps.LatLng( thelat, thelon ) );
}

The wider aim was to keep the record and its geographic position consistent. Related examples are collected in the project portfolio.

Back to top