This question already has an answer here:
Google Maps API V3: How to get region border coordinates (polyline) data? [duplicate]
(1 answer)
Closed 6 years ago.
Now on google.com/maps page when I click on the name/label of a country or city, this area are highlighted and opens left popup window with information.
Is it possible to realize the same thing with the Google Maps API?
To highlight label on mouse over, to highlight boundaries of country, state or city on click and to get selected by user label/name in js.
I looked Google Maps API JavaScript documentation but similar functionality not found.
Thanks!
Sorry for imprecise question.
Is there any way to set clickable country / city names on map with API, like on google.com/maps page?
Recommend a 'barebones' GMap class. As it happens I am just re-visiting this myself so try this code (it might need some changes)
/**
* 'GMap'
* extends googlemaps to allow simpler coding in other apps, should be loaded after the main google maps
* a new instance of a google map is contructed calling this with identical arguments to the standard class and then returned
* the map name must be unique and reserved as a global by the client code, eg var pagemap outside of function, so the external callbacks
* can use this name to run a function against, eg pagemap.queryMove_do
*
*/
function GMap(mapId, options) {
this.name = null;
this.map = null;
this.bounds = null; //view point boundary
this.gui_reg;
this.mapoptions;
this.calledbackonload;
this.polygons = {};
this.lngoffset = {'direction': '', referenceelem: ''};
this.infowindows = {};
this.infowindowindex = 0;
GMap.prototype.__construct = function (mapId, options) {
var mapbuffer = new Data_buffer(); //this has to be vague as 3rd party api data is not always the same format, simply return data to the logged map interface objects
if (typeof window.MAP_INTERFACE_CTRL === 'undefined') {//if not created, global constant to co-ordinate ajax requests with map objects and 3rd party API, and allow funneling back to local objects
window.MAP_INTERFACE_CTRL = new Data_buffer(); //this has to be vague as 3rd party api data is not always the same format, simply return data to the logged map interface objects
}
options.panControlOptions = model.array_defaults(
options.ctrlcfg,
{
position: google.maps.ControlPosition.RIGHT_BOTTOM
}
);
options.zoomControlOptions = model.array_defaults(
options.ctrlcfg,
{
position: google.maps.ControlPosition.RIGHT_CENTER
}
);
this.mapoptions = model.array_defaults(
options,
{
center: new google.maps.LatLng(54.5, -7),
zoom: 6,
minzoom: 15,
maxzoom: 21
}
);
this.gui_reg = {location: {address: new Array()}};
this.name = mapId;
this.map = new google.maps.Map(document.getElementById(mapId), this.mapoptions);
var selfobj = this;
google.maps.event.addListener(//processes client registration for registered events
this.map,
'dragend', //center changed causes too many requests
function () {
selfobj.update_client_go("location");
}
);
//create invisible scratchpad
if ($('#map_pool').length == 0) {
$('body').append("<div id='map_pool'></div>");
$('#map_pool').hide();
}
/*
callback_obj = this;
google.maps.event.addListener(this.map, 'tilesloaded', function(evt) {
callback_obj.map.setZoom(9);
});*/
}
/*****************************************************************/
/* METHODS FOR BOTH GMAP AND CLIENT */
/*****************************************************************/
/*
* defines offset of mapcentre away from an element such as custom controls
* which overlay a large amount of map
* positive or negative according to offset direction where the element might be on the
* left or the right
*/
GMap.prototype.set_lng_offset = function (elemid, dir) {
var direction = 'right';
if (dir == 'left') {
direction = dir;
}
this.lngoffset = {direction: direction, referenceelem: elemid};
}
/*
* returns lng offset according to definitions and current map situation
*/
GMap.prototype.calc_lng_offset = function () {
//get lng width of map
var eastlng = this.map.getBounds().getNorthEast().lng();
var westlng = this.map.getBounds().getSouthWest().lng();
var lngspan;
if (westlng <= 0 && eastlng >= 0) {
lngspan = Math.abs(westlng) + eastlng;
} else if (westlng >= 0 && eastlng <= 0) {
lngspan = (180 - westlng) + (180 - Math.abs(eastlng));
} else if (westlng < 0 && eastlng < 0) {
lngspan = Math.abs(eastlng) + westlng;
} else {
lngspan = eastlng - westlng;
}
//get px width of map and referenced element
var mapwidth = $("#" + this.name).width();
var offsetpx = $("#" + this.lngoffset.referenceelem).width();
//convert this px to lng offset for map by multiplying by lngpx
var lngperpx = lngspan / mapwidth; //calc lng/px as lngpx
//calc the offset lng
var offsetlng = offsetpx * lngperpx;
if (this.lngoffset.direction == 'left') {
var offsetlng = 0 - offsetlng;
}
return offsetlng;
}
/*
* returns an object with north south east west and centre coordinates of the current view
* args.precision states number of figures after decimal returned - if not give a full result is returned.
* #returns {float north,float east,float south,float west,float lat,float lng}
*/
GMap.prototype.get_viewport_coords = function (args) {
if (args == undefined) {
args = {};
}
var coords = {};
coords.north = this.map.getBounds().getNorthEast().lat();
coords.east = this.map.getBounds().getNorthEast().lng();
coords.south = this.map.getBounds().getSouthWest().lat();
coords.west = this.map.getBounds().getSouthWest().lng();
coords.lat = this.map.getCenter().lat();
coords.lng = this.map.getCenter().lng();
if (args.precision != undefined) {
coords.north = coords.north.toFixed(args.precision);
coords.east = coords.east.toFixed(args.precision);
coords.south = coords.south.toFixed(args.precision);
coords.west = coords.west.toFixed(args.precision);
}
return coords;
}
/*
* 'map_query'
* takes a text query and queries to google for info
*
* required due to APIs not being the same, a uuid is registered globally and a global process used to redirect
* because of this the global process must identify a map object by UUID and only expect a single argument as an object literal (JSON)
* this is then passed back to the object that initiated the ajax or UUID request
*
* #param OBJECT query_cfg to send to gmap geocode
* #param string query_cfg.type states what type of query and what action to take, allowing multiple use for this method
* #param string query_cfg.query_data a string containing address data such as '<postcode>, <street>, <property>'
* #param string query_cfg.callback to return the data to
*/
GMap.prototype.geo_query = function (query_cfg) {
//package pre query data and set defaults if not set
query_cfg = model.array_defaults(
query_cfg,
{
clientid: this.name,
callback: 'update_client_do'//client code can set its own callback,else set callback to this
}
);
var geo_queryid = MAP_INTERFACE_CTRL.callback_push(query_cfg);
switch (query_cfg.type) {
case "location"://returns location data at given lat lng coords
var gcoder = new google.maps.Geocoder();
gcoder.geocode({'location': {lat: query_cfg.lat, lng: query_cfg.lng}}, function (results, status) {
var server_response = {results: results, status: status};
eval("MAP_INTERFACE_CTRL.callback_pop('" + geo_queryid + "',server_response)");
});
break;
case "address_latlng":
var gcoder = new google.maps.Geocoder();
gcoder.geocode({'address': query_cfg.query_data}, function (results, status) {
var server_response = {results: results, status: status};
eval("MAP_INTERFACE_CTRL.callback_pop('" + geo_queryid + "',server_response)");
});
break;
case "sv_pano_latlng"://gets streetview pano for given lat lng
var sv_service = new google.maps.StreetViewService();
sv_service.getPanoramaByLocation(query_cfg.latlng, query_cfg.radius, function (results, status) {
var server_response = {results: results, status: status};
eval("MAP_INTERFACE_CTRL.callback_pop('" + geo_queryid + "',server_response)");
});
break;
case "viewport_range"://returns lat lng for viewport and centre to callback
var gcoder = new google.maps.Geocoder();
var bounds = this.bounds;
var center = this.map.getCenter();
gcoder.geocode({address: change_request}, function (results, status) {
callback_obj.viewport_range_do(results, status)
});
break;
}
}
/*
* 'formatted_geo_result'
* interprets geocoded results to a standard uniform independent of map api
* #param OBJECT geo_result data returned (in this map interface from google)
*/
GMap.prototype.formatted_geo_result = function (geodata) {
var formatted = {};
var itemdefaults = {
address: '',
lat: null,
lng: null
}
for (var i in geodata.results) {
var item = {
address: geodata.results[i].formatted_address,
lat: geodata.results[i].geometry.location.lat(),
lng: geodata.results[i].geometry.location.lng(),
}
formatted[i] = model.array_defaults(item, itemdefaults);
}
return formatted;
}
/*
* 'map_change'
* takes a request and processes and changes map accordingly
*
* callerobj is optional, if not given then callback will be back to this map object
* if a callerobj is given this must be to an object which can accept it
* (if callerobj is a top level function it needs to pass the object as keyword window - not 'window')
* The callback method is named as query type with _do appended eg address_move_do
* Some query types require the caller object to handle the query and DO NOT have a method defined in the map object
* If this map object DOES have the callback but a callback_obj is given then it will override this map object
*
* #param string change_request free text to send to gmap object
* #param string change_type states what type of query and what action to take, allowing multiple use for this method
* #param string callback function to run on change completion.
*/
GMap.prototype.map_change = function (change_type, change_data, callback) {
switch (change_type) {
case "lat_lng":
this.map.panTo({lat: change_data.latlng.lat, lng: change_data.latlng.lng + this.calc_lng_offset()});
break;
case "address"://to address_latlng as translation for this map object
this.geo_query({type: "address_latlng", clientid: this.name, callback: "map_change_do", change_type: change_type, query_data: change_data});
break;
}
if (callback != undefined) {
this.loadedCallback(callback);
}
}
/*
* 'map_change_do'
* processes any ajax return required by map_change
*
* #param string change_request free text to send to gmap object
* #param string change_type states what type of query and what action to take, allowing multiple use for this method
*/
GMap.prototype.map_change_do = function (pre_data, callback_data) {
switch (pre_data.change_type) {
case "address":
this.map_change("lat_lng", {latlng: {lat: callback_data.results[0].geometry.location.lat(), lng: callback_data.results[0].geometry.location.lng()}}); //callback_data.results[0].geometry.location.lat()
break;
}
}
/*
* permanently deletes all objects such as map markers (pins) etc
*/
GMap.prototype.clearmapobjects = function () {
this.map.clearMarkers();
}
GMap.prototype.panotestA = function () {
var locquery = "framlingham tech centre";
this.map_change("address", locquery);
this.map.setZoom(20);
this.geo_query("location", locquery)
this.geo_query({type: "address_latlng", clientid: this.name, callback: "panotestB", query_data: locquery});
}
GMap.prototype.panotestB = function (pre_data, callback_data) {
var pin = this.add_pin(callback_data.results[0].geometry.location.lat(), callback_data.results[0].geometry.location.lng(), {infopop: {content: 'hello', streetview: true}});
}
/*
* 'add_pin'
* adds a pin to a map
*
* #param float lat is latitude position
* #param float lng is longitude position
* #param object info_args other pin constructs such as the info displayed while clicking it
* #return object marker
*/
GMap.prototype.add_pin = function (lat, lng, info_args) {
info_args = model.array_defaults(
info_args,
{
width: 16,
height: 16,
animation: google.maps.Animation.DROP,
icon: {
url: "http://" + ROOTUC + "//css/images/icons/ROYOdot_a.gif"
},
infopop: null
}
);
/*
var icon = {
url: info_args.icon.url,
size: new google.maps.Size(20, 20),
origin: new google.maps.Point(0, 0),
anchor: new google.maps.Point(0, 0)
};
*/
var iconImage = new google.maps.MarkerImage(
info_args.icon.url, // url to image inc http://
null, // desired size
null, // offset within the scaled sprite
null, // anchor point is half of the desired size
new google.maps.Size(info_args.width, info_args.height) // required size
);
var pin = new google.maps.Marker({
position: new google.maps.LatLng(lat, lng),
map: this.map,
title: info_args.label,
icon: iconImage,
optimized: false, //to allow for animations
animation: info_args.animation,
});
if (info_args.infopop != null) {
/*content is forced into div with black font as google default
* is white text on white background ?!?!?!
*/
info_args.infopop = model.array_defaults(
info_args.infopop,
{
fontcolor: '#000000',
infowidth: '40em',
streetview: false
});
//standard google defs
var infowindow = new google.maps.InfoWindow();
//extra seperate data ref for client apps
infowindow.metadata = {
parent: this,
parentid: this.infowindowindex,
};
this.infowindows[this.infowindowindex] = {
infowidth: info_args.infopop.infowidth,
content: info_args.infopop.content,
fontcolor: info_args.infopop.fontcolor,
latlng: {lat: lat, lng: lng},
streetview: info_args.infopop.streetview
};
this.infowindowindex++;
//build for pin click
google.maps.event.addListener(pin, 'click', function () {
var infodata = infowindow.metadata.parent.infowindows[infowindow.metadata.parentid];
//build content
var infocontent = "<div id='" + infowindow.metadata.parent.name + "infowindow" + (infowindow.metadata.parentid) + "' style='color:" + infodata.fontcolor + ";width:" + infodata.infowidth + "'>";
infocontent += infodata.content;
if (infodata.streetview) {
infocontent += "<div id='" + infowindow.metadata.parent.name + "infowindowsvframe" + (infowindow.metadata.parentid) + "' class='infostreetviewframe' >";
infocontent += ". . . loading view</div>";
}
infocontent += "</div>";
infowindow.setContent(infocontent);
infowindow.open(this.map, pin);
if (infodata.streetview) {
var service = new google.maps.StreetViewService();
infowindow.metadata.parent.geo_query({type: "sv_pano_latlng", latlng: infodata.latlng, radius: 50, clientid: infowindow.metadata.parent.name, streetviewframe: infowindow.metadata.parent.name + "infowindowsvframe" + (infowindow.metadata.parentid), callback: "add_streetview_do"});
}
});
}
}
/*
* 'add_streetview'
* for flexibility addition of a streetview into a named dom element id
*
* #param object args
* #param.args string elemid the element to put the streetview into
*
*/
GMap.prototype.add_streetview_do = function (predata, callbackdata) {
var svelement = document.getElementById(predata.streetviewframe);
if (callbackdata.status == google.maps.StreetViewStatus.OK) {
var targetPOVlocation = predata.latlng
var svLatLng = {lat: callbackdata.results.location.latLng.lat(), lng: callbackdata.results.location.latLng.lng()};
// var svLatLng = new google.maps.LatLng(callbackdata.results.location.latLng.lat(), callbackdata.results.location.latLng.lng());
//var svLatLng = callbackdata.results.location.latLng;
var povyaw = this.getBearing(svLatLng, targetPOVlocation);
var sv = new google.maps.StreetViewPanorama(svelement);
var svoptions = {
position: svLatLng,
addressControl: false,
linksControl: false,
panControl: false,
zoomControlOptions: {
style: google.maps.ZoomControlStyle.SMALL
},
pov: {
heading: povyaw,
pitch: 10,
zoom: 1
},
enableCloseButton: false,
visible: true
};
sv.setOptions(svoptions);
} else {
svelement.innerHTML = "no streetview available";
}
}
/*
* 'getBearing'
* calcs bearing from two latlngs
*
*/
GMap.prototype.getBearing = function (fromLatLng, targetLatLng) {
var DEGREE_PER_RADIAN = 57.2957795;
var RADIAN_PER_DEGREE = 0.017453;
var dlat = targetLatLng.lat - fromLatLng.lat;
var dlng = targetLatLng.lng - fromLatLng.lng;
// We multiply dlng with cos(endLat), since the two points are very closeby,
// so we assume their cos values are approximately equal.
var bearing = Math.atan2(dlng * Math.cos(fromLatLng.lat * RADIAN_PER_DEGREE), dlat)
* DEGREE_PER_RADIAN;
if (bearing >= 360) {
bearing -= 360;
} else if (bearing < 0) {
bearing += 360;
}
return bearing;
}
/*
* 'add_polygon'
* adds a polygon to a map using an array of lat lng coords
* these are pushed in the order they appear in the locationarray
*
* #param array locationarray - which event/change type triggers update of this element
*
*/
GMap.prototype.add_polygon = function (args) {
//construct and store polygon
var polyobj = model.array_defaults(args, {name: "polygon", polygon: null, infohtml: null, box: null, focuson: false});
//remove any same named polygon
if (this.polygons[args.name] != undefined) {
this.polygons[args.name].polygon.setMap(null);
this.polygons[args.name] = null;
}
var newpolygon = new Array();
polyobj.box = new google.maps.LatLngBounds();
for (var l in args.polygon) {
var point = new google.maps.LatLng(args.polygon[l].lat, args.polygon[l].lng);
newpolygon.push(point);
polyobj.box.extend(point);
}
polyobj.polygon = new google.maps.Polygon(
{
map: this.map,
paths: newpolygon,
strokeColor: '#00ff00',
strokeOpacity: 0.75,
strokeWeight: 2,
fillColor: '#00ff00',
fillOpacity: 0.15
}
);
this.polygons[args.name] = polyobj;
//set required changes to map
if (args.infohtml != null) {
// content is forced into div with black font as google default is white text on white background ?!?!?!
polyobj.info = new google.maps.InfoWindow(
{
content: "<div style='color:#000000'>" + args.infohtml + "</div>"
});
var selfobj = this;
google.maps.event.addListener(this.polygons[args.name].polygon, 'click', function (event) {
var point = event.latLng;
selfobj.polygons[args.name].info.setPosition(point);
selfobj.polygons[args.name].info.open(selfobj.map);
});
}
if (polyobj.focuson) {
this.map.fitBounds(polyobj.box);
}
}
/*****************************************************************/
/* methods for CLIENT --> GMAP change */
/*********************************************************S ********/
/*
* allows gui to register elements so when changes / events occur in the map gui, its
* parent gui can be updated.
* #param string change_type - which event/change type triggers update of this element
* #param string change_return - data required to be returned eg latlng or address etc
* #param string callback - the page element identifier to update
*/
GMap.prototype.register_elem = function (change_type, event_return_cfg) {
switch (change_type) {
case "location":
switch (event_return_cfg.return_type) {
case "address":
this.gui_reg.location.address.push(event_return_cfg);
break;
}
break;
}
}
/*****************************************************************/
/* methods for GMAP --> CLIENT change */
/*****************************************************************/
/*
* checks registered change type to send back to client
* #param string change_type - which event/change type triggers update of this element
* #param string data_request - data required to be returned
* #param string regelem - the parent gui identifier to update5
* #param object attr - which attribute to update eg for input, its value, for div its html
*/
GMap.prototype.update_client_go = function (change_type, data_request) {
switch (change_type) {
case "location":
var loc = this.get_viewport_coords();
this.geo_query({type: "location", lat: loc.lat, lng: loc.lng});
}
}
/*
* actions client_update
* #param string change_type - which event/change type triggers update of this element
* #param string data_request - data required to be returned
* #param string regelem - the parent gui identifier to update5
* #param object attr - which attribute to update eg for input, its value, for div its html
*/
GMap.prototype.update_client_do = function (pre_data, callback_data) {
if (callback_data.status != "ZERO_RESULTS") {
switch (pre_data.type) {
case "location":
var location_data_registrars = this.gui_reg.location;
for (var loc_type_list in location_data_registrars) {
switch (loc_type_list) {
case "address":
var address = callback_data.results[0].formatted_address;
for (var r in location_data_registrars[loc_type_list]) {
var event_return = location_data_registrars[loc_type_list][r];
if (event_return != undefined) {
event_return.address = this.address_parse("postcode", address);
if (event_return.callback != undefined) {
eval(event_return.callback + "(event_return)");
}
}
}
break;
}
}
break;
}
}
}
/*****************************************************************/
/* PRIVATE METHODS */
/*****************************************************************/
GMap.prototype.address_parse = function (required_part, address) {
var result = "";
address = address.split(", ");
var country = address[address.length - 1];
switch (country) {
case "UK":
switch (required_part) {
case "postcode":
result = address[address.length - 2];
result = result.substr(result.indexOf(" ") + 1);
break;
}
break;
}
return result;
}
GMap.prototype.loadedCallback = function (callback) {
this.calledbackonload = false;
google.maps.event.addListenerOnce(this.map, 'tilesloaded', function () {
google.maps.event.clearListeners(this.map, 'idle');
eval(callback + '()');
});
google.maps.event.addListenerOnce(this.map, 'idle', function () {
eval(callback + '()'); //add idle as a catch all
});
/*
google.maps.event.addListenerOnce(this.map, 'idle', function() {
eval(callback + '()')
});
*/
}
/*
* zooms map to next level from current depending on dir being '+' or '-'
*/
GMap.prototype.incrementZoom = function (dir, callback) {
var newZoom = this.map.getZoom();
if (dir == '+') {
newZoom++;
}
if (dir == '-') {
newZoom--;
}
this.map.setZoom(newZoom);
if (callback != undefined) {
this.loadedCallback(callback);
}
}
/*
* zooms map to fully zoomed in
*/
GMap.prototype.zoomMax = function (callback) {
this.map.setZoom(this.mapoptions.maxzoom);
if (callback != undefined) {
this.loadedCallback(callback);
}
}
/*
* zooms map to fully zoomed out
*/
GMap.prototype.zoomMin = function (callback) {
this.map.setZoom(this.mapoptions.minzoom);
if (callback != undefined) {
this.loadedCallback(callback);
}
}
/*
* zooms map to contain the given box as per gmaps southwest / northeast corner specs
*/
GMap.prototype.boxZoom = function (latfrom, latto, lngfrom, lngto) {
var bottomleft = {lat: latfrom, lng: lngfrom};
var topright = {lat: latto, lng: lngto};
var box = new google.maps.LatLngBounds(bottomleft, topright);
this.map.fitBounds(box);
}
/*
* forces zoom in to min max params and returns true if it has been constrained
* additional callback if required for when zoom to constraints has finished
*/
GMap.prototype.constrainzoom = function (callback) {
if (this.map.getZoom() > this.mapoptions.maxzoom) {
this.map.setZoom(this.mapoptions.maxzoom);
} else if (this.map.getZoom() < this.mapoptions.minzoom) {
this.map.setZoom(this.mapoptions.minzoom);
} else {
return false;
}
if (callback != undefined) {
this.loadedCallback(callback);
}
return true;
}
/*add extras to generic map object*/
google.maps.Map.prototype.clearMarkers = function () {
for (var i = 0; i < this.markers.length; i++) {
this.markers[i].setMap(null);
}
this.markers = new Array();
};
/*
* places an existing div by id as a map control
*/
GMap.prototype.addcontrol = function (divid, posn) {
if (posn == undefined) {
posn = google.maps.ControlPosition.TOP_LEFT;
}
this.map.controls[posn].push(document.getElementById(divid));
}
/* translate normal functions to internal map object */
/* trigger setup */
this.__construct(mapId, options);
}
I want to get the Feature of a Layer by coordinate.
Furthermore I want to open this feature in a popup, which I have solved so far by an onclick event. But I want to realize by giving the coordinates of a feature and opening the popup of the featue.
I have a layer with the map and a layer with the features:
if (trackMap != null) {
for (var i = 0; i < trackMap.length; i++) {
var trackInfo = trackMap[i];
lat = parseFloat(trackInfo.lat);
lon = parseFloat(trackInfo.lon);
var layergpx = new ol.layer.Vector({
source: new ol.source.Vector({
parser: new ol.parser.GPX(),
url: '${contextRoot}/gps/gpx2' + trackInfo.url
})
});
layers.push(layergpx);
}
}
I want to get the feature of this layer in another Javascript function.
How I open a pop up by clicking on the map:
/**
* The Click Event to show the data
*/
var element = document.getElementById('popup');
var popup = new ol.Overlay({
element: element,
positioning: ol.OverlayPositioning.BOTTOM_CENTER,
stopEvent: false
});
map.addOverlay(popup);
map.on('singleclick', function(evt) {
map.getFeatures({
pixel: evt.getPixel(),
layers: vectorLayers,
success: function(layerFeatures) {
var feature = layerFeatures[0][0];
if (feature) {
var geometry = feature.getGeometry();
var coord = geometry.getCoordinates();
popup.setPosition(coord);
$(element).popover({
'placement': 'top',
'html': true,
'content': feature.get('desc')
});
$(element).popover('show');
} else {
$(element).popover('destroy');
}
}
});
});
But I want this feature not to be opened by clicking on it on the map, but by entering a coordinate in a textfield and the map opens this pop up, like in the onclick event.
Take a look at this example to see if it helps you:
http://openlayers.org/en/latest/examples/kml.html
var displayFeatureInfo = function(pixel) {
map.getFeatures({
pixel: pixel,
layers: [vector],
success: function(featuresByLayer) {
var features = featuresByLayer[0];
var info = [];
for (var i = 0, ii = features.length; i < ii; ++i) {
info.push(features[i].get('name'));
}
document.getElementById('info').innerHTML = info.join(', ') || ' ';
}
});
map.getFeatures() has this success callback where it delivers the features of the layers specified in layers: [vector]. Customize it at will to get what you need.
=== Update ===
In the OpenLayers 3's Map object you have a function: getPixelFromCoordinate
/**
* #param {ol.Coordinate} coordinate Coordinate.
* #return {ol.Pixel} Pixel.
*/
ol.Map.prototype.getPixelFromCoordinate = function(coordinate) {
var frameState = this.frameState_;
if (goog.isNull(frameState)) {
return null;
} else {
var vec2 = coordinate.slice(0, 2);
return ol.vec.Mat4.multVec2(frameState.coordinateToPixelMatrix, vec2, vec2);
}
};
I am trying to make a marker move(not disappear and appear again) on the map as a vehicle moves on the road.
I have two values of latLng and I want to move the marker between the two till the next point is sent by the vehicle. And then repeat the process again.
What I tried:[This is not a very efficient way, I know]
My thought was to implement the above using the technique in points below:
1) Draw a line between the two.
2) Get the latLng of each point on 1/10th fraction of the polyline.
3) Mark the 10 points on the map along with the polyline.
Here is my Code:
var isOpen = false;
var deviceID;
var accountID;
var displayNameOfVehicle;
var maps = {};
var lt_markers = {};
var lt_polyLine = {};
function drawMap(jsonData, mapObj, device, deleteMarker) {
var oldposition = null;
var oldimage = null;
var arrayOflatLng = [];
var lat = jsonData[0].latitude;
var lng = jsonData[0].longitude;
//alert(jsonData[0].imagePath);
var myLatLng = new google.maps.LatLng(lat, lng);
if (deleteMarker == true) {
if (lt_markers["marker" + device] != null) {
oldimage = lt_markers["marker" + device].getIcon().url;
oldposition = lt_markers["marker" + device].getPosition();
lt_markers["marker" + device].setMap(null);
lt_markers["marker" + device] = null;
}
else {
console.log('marker is null');
oldimage = new google.maps.MarkerImage(jsonData[0].imagePath,
null,
null,
new google.maps.Point(5, 17), //(15,27),
new google.maps.Size(30, 30));
oldposition = myLatLng;
}
}
var image = new google.maps.MarkerImage(jsonData[0].imagePath,
null,
null,
new google.maps.Point(5, 17), //(15,27),
new google.maps.Size(30, 30));
lt_markers["marker" + device] = new google.maps.Marker({
position: myLatLng,
icon: image,
title: jsonData[0].address
});
if (oldposition == myLatLng) {
alert('it is same');
lt_markers["marker" + device].setMap(mapObj);
mapObj.panTo(myLatLng);
}
else {
alert('it is not same');
var markMarker = null;
var i = 10;
for (i = 10; i <= 100; i + 10) {
//-------
// setTimeout(function() {
if (markMarker != null) {
markMarker.setMap(null);
markMarker = null;
}
alert('inside the loop');
var intermediatelatlng = mercatorInterpolate(mapObj, oldposition, myLatLng, i / 100);
alert('Intermediate Latlng is :' + intermediatelatlng);
arrayOflatLng.push(intermediatelatlng);
var flightPath = new google.maps.Polyline({
path: arrayOflatLng,
strokeColor: "#FFFFFF",
strokeOpacity: 1.0,
strokeWeight: 1
});
flightPath.setMap(mapObj);
if (i != 100) {
markMarker = new google.maps.Marker({
position: intermediatelatlng,
icon: image,
title: jsonData[0].address,
map: mapObj
});
}
else {
markMarker = new google.maps.Marker({
position: intermediatelatlng,
icon: oldimage,
title: jsonData[0].address,
map: mapObj
});
}
mapObj.panTo(intermediatelatlng);
//--------
// }, 1000);
}
}
}
function mercatorInterpolate(map, latLngFrom, latLngTo, fraction) {
// Get projected points
var projection = map.getProjection();
var pointFrom = projection.fromLatLngToPoint(latLngFrom);
var pointTo = projection.fromLatLngToPoint(latLngTo);
// Adjust for lines that cross the 180 meridian
if (Math.abs(pointTo.x - pointFrom.x) > 128) {
if (pointTo.x > pointFrom.x)
pointTo.x -= 256;
else
pointTo.x += 256;
}
// Calculate point between
var x = pointFrom.x + (pointTo.x - pointFrom.x) * fraction;
var y = pointFrom.y + (pointTo.y - pointFrom.y) * fraction;
var pointBetween = new google.maps.Point(x, y);
// Project back to lat/lng
var latLngBetween = projection.fromPointToLatLng(pointBetween);
return latLngBetween;
}
Problems Faced:
1) The marker is not showing up on the map because the process of plotting and removal of marker is so fast that the marker is not visisble on screen. I've tried setTimeOut, and It does not help at all.
2) if I alow the browser to run this code for more than 5 minutes, the browser crashes.
Note: The Above function is called every 10 seconds using setInterval.
What Can be a better solution? Please Help..
For the marker to move relatively smoothly, you need to
Update more than every 1/10 fraction of the polyline (at least every few pixels)
Call the update method more frequently
Don't delete and re-add the marker
For example, something like:
var counter = 0;
interval = window.setInterval(function() {
counter++;
// just pretend you were doing a real calculation of
// new position along the complex path
var pos = new google.maps.LatLng(35, -110 + counter / 100);
marker.setPosition(pos);
if (counter >= 1000) {
window.clearInterval(interval);
}
}, 10);
I made a simple example at http://jsfiddle.net/bmSbU/2/ which shows a marker moving along a straight path. If this is what you want, most of your code above regarding where along the line you are can be reused (or check out http://broady.github.io/maps-examples/points-along-line/along-directions.html )
You can use marker-animate-unobtrusive library to make markers
smoothly transition from one location to another (instead of reappearing).
You could initialize your marker like that:
var marker = new SlidingMarker({
//your original marker options
});
Just call marker.setPosition() each time new vehicle's coordinate arrive.
P.S. I'm the author of the library.
Why not keep the existing Marker/ MarkerImage and call setPosition() to move it, either on a timer or as the position changes?
Deleting it & recreating it is what causes it to flash/ flicker and eventually crash. If you keep the same instance but just move it, you should do much better.
See: Marker.setPosition()
https://developers.google.com/maps/documentation/javascript/reference#Marker