I'm developing an Android application containing a Webview. The HTML corresponding to the webview contains mainly JavaScript code ; it retrieves a map of a building from a Geoserver. I use Leaflet to display the different layers. Each base layer is a floor.
I'm adding 2 overlays to the base layers from Android ; one is a heatmap, the other is a set of markers representing the locations of some sensors. Thanks to a JavaScript interface in Android, the JavaScript code can get datasets created from Android.
It works when I display only ONE of the overlays. As soon as I want to display both overlays, or after I displayed an overlay, hid it, and display the other, the application crashes. I have different errors according to the overlay added first.
Something really strange : it only happens with my tablet (which is very cheap), not with the AVD.
Here is my JavaScript code :
<script type="text/javascript" charset="utf-8">
var map, baseLayers, heatmapLayer, sensorsLayer;
var ground_floor = new L.tileLayer.wms('http://192.168.1.16/geoserver/wms',
{
layers: 'ground_floor',
format: 'image/png'
});
var first_floor = new L.tileLayer.wms('http://192.168.1.16/geoserver/wms',
{
layers: 'first_floor',
format: 'image/png'
});
var second_floor = new L.tileLayer.wms('http://192.168.1.16/geoserver/wms',
{
layers: 'second_floor',
format: 'image/png'
});
var current_floor = ground_floor;
baseLayers = {
"Ground floor": ground_floor,
"First floor": first_floor,
"Second floor": second_floor
};
map = new L.Map('map', {
center: new L.LatLng(-45, 170),
zoom: 30,
layers: [ground_floor],
crs: L.CRS.EPSG900913
}).setView([-45.8668664, 170.5185323], 31);
var control = L.control.layers(baseLayers).addTo(map);
map.on('baselayerchange', onBaseLayerChanged);
// Called when the base layer (meaning the floor) is changed
function onBaseLayerChanged(event) {
// Update the current floor
if (event.layer == ground_floor)
current_floor = ground_floor;
else if (event.layer == first_floor)
current_floor = first_floor;
else if (event.layer == second_floor)
current_floor = second_floor;
else
Android.debug("Wrong base layer");
Android.debug("yo1");
// Update the heatmap and sensors' location
// if they are displayed
if (map.hasLayer(heatmapLayer)) {
removeHeatmap();
addHeatmap();
}
Android.debug("yo2");
if (map.hasLayer(sensorsLayer)) {
Android.debug("yo3");
removeSensors();
addSensors();
Android.debug("yo4");
}
}
function addHeatmap() {
heatmapLayer = L.TileLayer.heatMap({
// radius could be absolute or relative
// absolute: radius in meters, relative: radius in pixels
radius: { value: 5, absolute: true },
opacity: 0.8,
gradient: {
0.45: "rgb(0,0,255)",
0.55: "rgb(0,255,255)",
0.65: "rgb(0,255,0)",
0.95: "yellow",
1.0: "rgb(255,0,0)"
}
});
var dataSet;
if (current_floor == ground_floor)
dataSet = JSON.parse(Android.getDataSetGroundFloor());
else if (current_floor == first_floor)
dataSet = JSON.parse(Android.getDataSetFirstFloor());
else if (current_floor == second_floor)
dataSet = JSON.parse(Android.getDataSetSecondFloor());
else
Android.debug("Error getDataSet : wrong floor");
heatmapLayer.setData(dataSet.data);
control.addOverlay(heatmapLayer, "Heatmap");
heatmapLayer.addTo(map);
}
function removeHeatmap() {
control.removeLayer(heatmapLayer);
map.removeLayer(heatmapLayer);
}
function addSensors() {
var sLat, sLon;
var markers = new Array();
if (current_floor == ground_floor) {
sLat = JSON.parse(Android.getLatitudesGroundFloor());
sLon = JSON.parse(Android.getLongitudesGroundFloor());
}
else if (current_floor == first_floor) {
sLat = JSON.parse(Android.getLatitudesFirstFloor());
sLon = JSON.parse(Android.getLongitudesFirstFloor());
}
else if (current_floor == second_floor) {
sLat = JSON.parse(Android.getLatitudesSecondFloor());
sLon = JSON.parse(Android.getLongitudesSecondFloor());
}
else
Android.debug("Error getCoordinates : wrong floor");
for (i=0; i<sLat.length && i<sLon.length; i++) {
var marker = L.marker([sLat[i],sLon[i]]);
markers.push(marker);
}
sensorsLayer = L.layerGroup(markers);
control.addOverlay(sensorsLayer, "Sensors");
sensorsLayer.addTo(map);
}
function removeSensors() {
control.removeLayer(sensorsLayer);
map.removeLayer(sensorsLayer);
}
</script>
If I add the heatmap first, and then the sensors I have a NullPointerException on "sensorsLatFloor" of the following code :
public JSONArray getLatitudesGroundFloor() {
if (!isSensorsQueryDone())
new SelectSensorsLocationATask(SensorsFragment.this).execute();
// Wait for the end of the query
while (!querySuccessful) {}
JSONArray latJSON = null;
latJSON = new JSONArray(sensorsLatGroundFloor);
return latJSON;
}
This is for the ground floor but it does the same for every floor. "sensorsLatGroundFloor" is an ArrayList filled by the AsyncTask SelectSensorsLocationATask after a query in the local database. The code works, since it works when I only want to display sensors.
When I display the sensors first, and then the heatmap, the app crashes and I have the following error in the LogCat :
JNI ERROR (app bug): accessed staled weak global reference 0xffffffff (index 65535 in a table of size 8)
VM aborting
Fatal signal 11 (SIGSEV) at 0xdeadd00d
This is very odd, because I don't manipulate JNI code at all...
Besides, I have another error, which must be very stupid but I don't figure out why it doesn't work. Have a look at this part of my JavaScript code :
// Called when the base layer (meaning the floor) is changed
function onBaseLayerChanged(event) {
// Update the current floor
if (event.layer == ground_floor)
current_floor = ground_floor;
else if (event.layer == first_floor)
current_floor = first_floor;
else if (event.layer == second_floor)
current_floor = second_floor;
else
Android.debug("Wrong base layer");
Android.debug("yo1");
// Update the heatmap and sensors' location
// if they are displayed
if (map.hasLayer(heatmapLayer)) {
removeHeatmap();
addHeatmap();
}
Android.debug("yo2");
if (map.hasLayer(sensorsLayer)) {
Android.debug("yo3");
removeSensors();
addSensors();
Android.debug("yo4");
}
}
This function is supposed to update the overlay(s) displayed when I switch the base layer (meaning the floor). It works with the heatmap, but not with the sensors... If the heatmap is not on the map, the function is finished, it doesn't even look at my second if. You can see my "Android.debug", it displays in the LogCat the message I put as parameter. Here, the LogCat only displays "yo1".
EDIT : I figured something out about this last error. The problem seems to be the Leaflet function "hasLayer". If the map has the layer indicated, it returns true. So in my mind, if it doesn't, it's supposed to return false... It makes sense. But instead of it, it makes the code bug, so the code after the function is ignored. Either I'm making a mistake that I don't see when I call it, or Leaflet made a useless function... So I must have done a silly mistake but I don't find it !
I hope I've been clear enough to let you understand my problems... Let me know if you need more Android code, although I don't think it would be helpful.
Thanking you in advance.
* PROBLEM FIXED *
The errors were caused by a problem of task synchronisation in the Android code. You can see that I wait for the end of the query with the variable "querySuccessful". Actually I was manipulating this variable from the AsyncTask only : I set it to false at the beginning and true at the end. Now I set it to true before I start the AsyncTask, and the application works, I have no more errors.
I still have some problems with the Leaflet function hasLayer ; it works when I switch the base layer once, sometimes twice, but then it stops working. But apparently it might be an error from Leaflet, which will be fixed in the next release.
Related
So I have a function for inserting a tile layer
var layer = new TileLayer({
// url: "https://servicesbeta.arcgisonline.com/arcgis/rest/services/Firefly_World_Imagery/MapServer"
});
map.add(layer);
As you zoom in and out, the scale changes.
I wrote a function that says if the minscale is 500,000 then please return a value of 32 and else return a value of 7.
var scale_condition= {
if (layer.minScale<=500000){
return 32;
},
else{
return 7;
},
};
Instead I get nothing. This does not seem to difficult, so I' am wondering what am I doing wrong?
I'm migrating from Google Maps API to Apple MapKit JS for the simple reason I have a developer account with them and they offer more free hits.
Anyway, actual examples of MapKit JS are a bit thin (or at least Google isn't finding them - draw what conspiracy theories you will), so although I've got the basics going of displaying an embeded map, I can't seem to do the next step which is route between two points (Apple's documentation also seems impenetrable as they don't show examples).
Here's my script for a basic map:
<script>
mapkit.init({
authorizationCallback: function(done) {
done('[MY-TOKEN]');
}
});
var MarkerAnnotation = mapkit.MarkerAnnotation
var myMarker = new mapkit.Coordinate(55.9496320, -3.1866360)
var myRegion = new mapkit.CoordinateRegion(
new mapkit.Coordinate(55.9496320, -3.1866360),
new mapkit.CoordinateSpan(0.003, 0.003)
);
var map = new mapkit.Map("map");
var myAnnotation = new MarkerAnnotation(myMarker, { color: "#9b6bcc", title: "theSpace On The Mile"});
map.showItems([myAnnotation]);
map.region = myRegion;
</script>
Now I want to:
• Show a walking route between two points
• Include waypoints on the route
Could someone show the code that would achieve this? Once I can see an example I know I'll get it ;-)
Ok, so I've found a solution to this so sharing it here for the benefit of others.
Let's start by saying Apple's MapKit JS doesn't appear to have a waypoints option as offered by Google Maps API - so the way around that is to create a map that stores the markers in an array and then routes from one to the next. The code stores the location of the last waypoint in a variable, and doesn't bother to draw a route to the last waypoint if this is the first one in the array (obviously).
<script>
// Initiallise MapKit - you'll need your own long-lived token for this
mapkit.init({
authorizationCallback: function(done) {
done('[MY-TOKEN]');
}
});
// Function to draw the route once MapKit has returned a response
function directionHandler(error, data) {
data["routes"].forEach(function(route, routeIdx) {
if (routeIdx !== 0) { return; }
overlays = [];
route['path'].forEach(function(path) {
// This styles the line drawn on the map
let overlayStyle = new mapkit.Style({
lineWidth: 3,
strokeColor: "#9b6bcc"
});
let overlay = new mapkit.PolylineOverlay(path, {
style: overlayStyle
});
overlays.push(overlay);
});
map.addOverlays(overlays);
});
}
// This asks MapKit for directions and when it gets a response sends it to directionHandler
function computeDirections(origin,destination) {
let directionsOptions = {
origin: origin,
destination: destination,
transportType: mapkit.Directions.Transport.Walking
};
directions.route(directionsOptions, directionHandler);
}
// This sets the initial region, but is overridden when all points have been potted to automatically set the bounds
var myRegion = new mapkit.CoordinateRegion(
new mapkit.Coordinate(55.9496320, -3.1866360),
new mapkit.CoordinateSpan(0.05, 0.05)
);
var map = new mapkit.Map("map");
map.region = myRegion;
var myAnnotations = [];
// lastWaypoint variable is 'unset' initially so the map doesn't try and find a route to the lastWaypoint for the first point of the route
var lastWaypoint = "unset";
var directions = new mapkit.Directions();
// Array of co-ordinates and label for marker
waypoints = [
{name:'Sofi’s Bar',lat:55.9746308,lon:-3.1722282},
{name:'TThe Roseleaf Cafe',lat:55.975992,lon:-3.173474},
{name:'Hemingway’s',lat:55.9763631,lon:-3.1706564},
{name:'Teuchter’s Landing',lat:55.9774693,lon:-3.1713826},
{name:'The King’s Wark',lat:55.9761425,lon:-3.1695419},
{name:'Malt and Hops',lat:55.975885,lon:-3.1698957},
{name:'The Carrier’s Quarters',lat:55.9760813,lon:-3.1685323},
{name:'Noble’s',lat:55.974905,lon:-3.16714},
{name:'The Fly Half',lat:55.9747906,lon:-3.1674496},
{name:'Port O’ Leith',lat:55.974596,lon:-3.167525}
];
// Loop through the array and create marker for each
waypoints.forEach(function(data) {
var myAnnotation = new mapkit.MarkerAnnotation(new mapkit.Coordinate(data['lat'],data['lon']), {
color: "#9b6bcc",
title: data['name']
});
myAnnotations.push(myAnnotation);
// As long as this isn't the first point on the route, draw a route back to the last point
if(lastWaypoint!="unset") {
computeDirections(lastWaypoint,new mapkit.Coordinate(data['lat'],data['lon']));
}
lastWaypoint = new mapkit.Coordinate(data['lat'],data['lon']);
});
map.showItems(myAnnotations);
</script>
This map is for a pub crawl around Leith, so the trasportType is 'Walking', but change that to 'Automobile' if you so wish.
With credit to Vasile whose MapKit JS Demo (https://github.com/vasile/mapkit-js-demo) helped me understand a lot more about the options.
A similar issue to that described in OpenLayers: destroyed features reappear after zooming in or out
Call destroyFeatures() or removeAllFeatures() (or both, in either order) on a vector layer. The features disappear from view. But then zoom in or out, and they reappear. For me, this only happens if a clustering strategy is being used. It almost seems as if the cluster feature is destroyed but the underlying features represented by that cluster are not, so, when you zoom in or out the clustering is recalculated from those underlying features and re-drawn.
Googling reveals a number of conversations among the developers of OpenLayers a few years ago concerning issues with destroyFeatures(). It's surprising that, even now, the issues don't seem to have been fully resolved.
I can get around the problem by destroying the whole layer (using destroy()) and then recreating it when needed. That's OK in my case, but I can imagine cases where such a blunderbuss approach might not be desirable.
In response to the request for a code sample, here is an abbreviated version of the code in the version that is working (ie, using destroy()). In the non-working version, I called destroyFeatures() instead (and did not set the layer to null). As stated above, this would erase the features initially, but if I then zoomed in or out using this.map.zoomIn(), the features would reappear.
Note 1: the functions are called from Objective-C via the JavaScript bridge.
Note 2: the JavaScript was generated using CoffeeScript.
(function() {
var addSightingsLayer, displayFeaturesForSightingsWithGeoJSONData, geoJSONFormat, load, map, projectionSphericalMercator, projectionWGS84, removeSightingsLayer, sightingsLayer;
addSightingsLayer = function() {
var context, layerStyle, layerStyleSelected, style, styleMap, styleSelected, yerClusteringStrategy;
if (!(this.sightingsLayer === null)) return;
yerClusteringStrategy = new OpenLayers.Strategy.Cluster({
distance: 10,
threshold: 2
});
this.sightingsLayer = new OpenLayers.Layer.Vector('Sightings', {
strategies: [yerClusteringStrategy]
});
this.map.addLayer(this.sightingsLayer);
style = {
// Here I define a style
};
context = {
// Here I define a context, with several functions depending on whether there is a cluster or not, eg:
dependentLabel: function(feature) {
if (feature.cluster) {
return feature.attributes.count;
} else {
return feature.attributes.name;
}
}, ....
};
layerStyle = new OpenLayers.Style(style, {
context: context
});
styleSelected = {
//...
};
layerStyleSelected = new OpenLayers.Style(styleSelected, {
context: context
});
styleMap = new OpenLayers.StyleMap({
'default': layerStyle,
'select': layerStyleSelected
});
this.sightingsLayer.styleMap = styleMap;
};
removeSightingsLayer = function() {
if (this.sightingsLayer === null) return;
this.sightingsLayer.destroy();
return this.sightingsLayer = null;
};
displayFeaturesForSightingsWithGeoJSONData = function(geoJSONData) {
if (this.sightingsLayer === null) JFOLMap.addSightingsLayer();
return this.sightingsLayer.addFeatures(this.geoJSONFormat.read(geoJSONData));
};
load = function() {
var lat, lon, osmLayer, zoom;
lat = ...;
lon = ...;
zoom = ...;
this.map = new OpenLayers.Map('mapDiv', {
controls: ...,
eventListeners: ...
});
osmLayer = new OpenLayers.Layer.OSM();
this.map.addLayer(osmLayer);
return this.map.setCenter(new OpenLayers.LonLat(lon, lat).transformWGS84ToSphericalMercator(), zoom);
};
OpenLayers.LonLat.prototype.transformWGS84ToSphericalMercator = function() {
return this.transform(JFOLMap.projectionWGS84, JFOLMap.projectionSphericalMercator);
};
OpenLayers.LonLat.prototype.transformSphericalMercatorToWGS84 = function() {
return this.transform(JFOLMap.projectionSphericalMercator, JFOLMap.projectionWGS84);
};
map = null;
sightingsLayer = null;
sightingsPopoverControl = null;
projectionWGS84 = new OpenLayers.Projection('EPSG:4326');
projectionSphericalMercator = new OpenLayers.Projection('EPSG:900913');
geoJSONFormat = new OpenLayers.Format.GeoJSON({
'externalProjection': projectionWGS84,
'internalProjection': projectionSphericalMercator
});
this.JFOLMap = {
map: map,
sightingsLayer: sightingsLayer,
projectionSphericalMercator: projectionSphericalMercator,
projectionWGS84: projectionWGS84,
geoJSONFormat: geoJSONFormat,
load: load,
addSightingsLayer: addSightingsLayer,
removeSightingsLayer: removeSightingsLayer,
displayFeaturesForSightingsWithGeoJSONData: displayFeaturesForSightingsWithGeoJSONData,
};
}).call(this);
Try this, it worked for me
layer.removeAllFeatures();
layer.destroyFeatures();//optional
layer.addFeatures([]);
I wonder whether someone may be able to help me please.
This page allows users to filter (via checkboxes), markers which are placed on the map. Clicking on any marker performs a 'reverse geocode' action and in conjunction with selecting the 'Search Locations' button, the user can then see POI's within a given radius of the clicked marker.
For demo purposes, if you select the 'Coin' checkbox, click the green marker, then select the 'Search Locations' button, the marker will bounce and the right hand sidebar will be populated with POI's.
The problem I'm having is that in Internet Explorer everything works fine, but when I try to run this in Chrome, the marker looses the 'bounce' functionality, the 'reverse geocode' doesn't run and in the error console I receive the following error:
Uncaught ReferenceError: reversegeocode is not defined at line 55 of my code which is:
reversegeocode(); I've done some reading on this and other sites and from reading the guidance I've tried changing this part of my code to this:
function geocodePosition(pos) {
var clickListener =
document.getElementById('findosgb36lat').value = this.mylat;
document.getElementById('findosgb36lon').value = this.mylon;
document.getElementById('address').value = this.myaddress;
if(bouncingMarker)
bouncingMarker.setAnimation(null);
if(bouncingMarker != this) {
this.setAnimation(google.maps.Animation.BOUNCE);
bouncingMarker = this;
}
else bouncingMarker = null;
}
geocoder.geocode({latLng: pos }, function(responses) {
if (responses && responses.length > 0) {
updateMarkerAddress(responses[0].formatted_address);
} else {
updateMarkerAddress('Cannot determine address at this location.');
}
});
}
function updateMarkerAddress(str) {
document.getElementById('address').value = str;
}
function getAddress(latlng) {
if (!geocoder) {
geocoder = new google.maps.Geocoder();
}
geocoder.geocode({ 'latLng': latlng }, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
// Looping through the result
for (var i = 0; i < results.length; i++) {
if (results[0].formatted_address) {
document.getElementById('address').value =
results[0].formatted_address;
}
}
}
}
)
}
But unfortunately, this doesn't work and actually creates more problems with syntax errors.
I'm relatively new to Javascript, so perhaps I've totally misunderstood,. But I just wondered whether someone could possibly take a look at this please and let me know where I'm going wrong?
Many thanks and kind regards
Code feedback:
What is the second line in your code: var clickListener = meant to do (something is missing)?
Both of your calls to geocoder.geocode pass an object with a property named: latLng; the object passed should match the structure of google.maps.GeocoderRequestapi-doc, which has properties named: address, bounds, location, and region. I suggest you change the name of these properties from: latLng to location.
I'm not sure of the context of the code shown, but the usage of this in this code section looks suspicious:
if(bouncingMarker != this) {
this.setAnimation(google.maps.Animation.BOUNCE);
bouncingMarker = this;
}
It looks like you are within a global function, so it looks like the use of this may not make sense here?
That's all I see after a first pass; hope this helps you -
Google Maps has the Drawing library to draw Polylines and Polygons and other things.
Example of this functionality here: http://gmaps-samples-v3.googlecode.com/svn-history/r282/trunk/drawing/drawing-tools.html
I want, when drawing and editing the polygon, to be able to delete one point/vertex on the path. The API docs haven't seemed to hint at anything.
Google Maps now provides a "PolyMouseEvent" callback object on events that are triggered from a Polygon or Polyline.
To build on the other answers which suggested a solution involving a right click, all you would need to do is the following in the latest versions of the V3 API:
// this assumes `my_poly` is an normal google.maps.Polygon or Polyline
var deleteNode = function(mev) {
if (mev.vertex != null) {
my_poly.getPath().removeAt(mev.vertex);
}
}
google.maps.event.addListener(my_poly, 'rightclick', deleteNode);
You'll notice that any complex calculations on whether or not we are near the point are no longer necesary, as the Google Maps API is now telling us which vertex we've clicked on.
Note: this will only work while the Polyline/Polygon is in edit mode. (Which is when the vertices you might want to delete are visible.)
As a final thought, you could consider using a click or double click event instead. "Click" is smart enough to not trigger on a drag, though using a single click trigger might still surprise some of your users.
This is currently an outstanding feature request (acknowledged by Google), issue 3760.
Here's my solution: http://jsbin.com/ajimur/10. It uses a function that adds a delete button to the passed in polygon (below the undo button).
Alternatively, someone suggested this approach: right-click to delete closest vertex, which works fine but is somewhat lacking in UI finesse. I built on the code from the link to check if the click was inside (or within 1 pixel of) the node - in a JSBin here: http://jsbin.com/ajimur/.
EDIT: as Amr Bekhit pointed out - this approach is currently broken, as the events need to be attached to the polygon.
I found Sean's code very simple and helpful. I just added a limiter to stop deleting when the user has only 3 nodes left. Without it, the user can get down to just one node, and can't edit anymore:
my_poly.addListener('rightclick', function(mev){
if (mev.vertex != null && this.getPath().getLength() > 3) {
this.getPath().removeAt(mev.vertex);
}
});
I ran into situations where I needed to delete nodes from polygons that contained multiple paths. Here's a modification of Sean's and Evil's code:
shape.addListener('rightclick', function(event){
if(event.path != null && event.vertex != null){
var path = this.getPaths().getAt(event.path);
if(path.getLength() > 3){
path.removeAt(event.vertex);
}
}
});
Just thought I'd contribute because I was looking for a solution for this too, here's my implementation:
if (m_event.hasOwnProperty('edge') && m_event.edge >= 0 &&
GeofenceService.polygon.getPath().getLength() > 3) {
GeofenceService.polygon.getPath().removeAt(m_event.edge);
return;
}
if (m_event.hasOwnProperty('vertex') && m_event.vertex >= 0 &&
GeofenceService.polygon.getPath().getLength() > 3) {
GeofenceService.polygon.getPath().removeAt(m_event.vertex);
return;
}
This allows for handling deletion of vertex nodes AND edge nodes, and maintains a minimum of a triangle formation polygon at all times by checking the path length > 3.
2020 Update
Google provides a working demo of this in their documentation which demonstrates how a to delete a vertex, or a point on the line, by right-clicking on a vertex to show a "Delete" menu.
Check out Deleting a Vertex
And the code for completeness (see their Github repo);
function initialize() {
const mapOptions = {
zoom: 3,
center: new google.maps.LatLng(0, -180),
mapTypeId: "terrain",
};
const map = new google.maps.Map(document.getElementById("map"), mapOptions);
const flightPlanCoordinates = [
new google.maps.LatLng(37.772323, -122.214897),
new google.maps.LatLng(21.291982, -157.821856),
new google.maps.LatLng(-18.142599, 178.431),
new google.maps.LatLng(-27.46758, 153.027892),
];
const flightPath = new google.maps.Polyline({
path: flightPlanCoordinates,
editable: true,
strokeColor: "#FF0000",
strokeOpacity: 1.0,
strokeWeight: 2,
map: map,
});
/**
* A menu that lets a user delete a selected vertex of a path.
*/
class DeleteMenu extends google.maps.OverlayView {
constructor() {
super();
this.div_ = document.createElement("div");
this.div_.className = "delete-menu";
this.div_.innerHTML = "Delete";
const menu = this;
google.maps.event.addDomListener(this.div_, "click", () => {
menu.removeVertex();
});
}
onAdd() {
const deleteMenu = this;
const map = this.getMap();
this.getPanes().floatPane.appendChild(this.div_);
// mousedown anywhere on the map except on the menu div will close the
// menu.
this.divListener_ = google.maps.event.addDomListener(
map.getDiv(),
"mousedown",
(e) => {
if (e.target != deleteMenu.div_) {
deleteMenu.close();
}
},
true
);
}
onRemove() {
if (this.divListener_) {
google.maps.event.removeListener(this.divListener_);
}
this.div_.parentNode.removeChild(this.div_);
// clean up
this.set("position", null);
this.set("path", null);
this.set("vertex", null);
}
close() {
this.setMap(null);
}
draw() {
const position = this.get("position");
const projection = this.getProjection();
if (!position || !projection) {
return;
}
const point = projection.fromLatLngToDivPixel(position);
this.div_.style.top = point.y + "px";
this.div_.style.left = point.x + "px";
}
/**
* Opens the menu at a vertex of a given path.
*/
open(map, path, vertex) {
this.set("position", path.getAt(vertex));
this.set("path", path);
this.set("vertex", vertex);
this.setMap(map);
this.draw();
}
/**
* Deletes the vertex from the path.
*/
removeVertex() {
const path = this.get("path");
const vertex = this.get("vertex");
if (!path || vertex == undefined) {
this.close();
return;
}
path.removeAt(vertex);
this.close();
}
}
const deleteMenu = new DeleteMenu();
google.maps.event.addListener(flightPath, "rightclick", (e) => {
// Check if click was on a vertex control point
if (e.vertex == undefined) {
return;
}
deleteMenu.open(map, flightPath.getPath(), e.vertex);
});
}