Blinking layers HERE vector maps - javascript

Background:
I'm working on integrating HERE maps with trucks restrictions to our web-based software solution.
Issue:
Halfway through the integration, I've noticed that when zooming-in/zooming-out the labels and truck restriction signs are blinking after reload. I've captured same behaviour in the example provided by HERE (link) themselves.
As the issue occurs in their own examples I think it is safe to state that it's not my implementation problem.
I've seen (also in their examples), that when their vector tiles are integrated using mapbox.gl, the zoom-in/zoom-out becomes smooth, but I would prefer to use their own javascript library due to other advantages (like traffic incidents display, etc.).
Question:
Anyone has any ideas how could that be solved?

I tried to reproduce the effect and i understand what you mean. From what i understand (i have not worked with HERE maps before), while a user zoom in/out the re-rendering process is the one that produces that single-flicker of a point sometimes.
There are 2 JSFiddle examples A and B, you will spot a difference but i think the problem not eliminated.
Example Code
/**
* A full list of available request parameters can be found in the Routing API documentation.
* see: http://developer.here.com/rest-apis/documentation/routing/topics/resource-calculate-route.html
*/
var routeRequestParams = {
routingMode: 'fastest',
transportMode: 'truck',
trafficMode: 'enabled',
origin: '40.7249546323,-74.0110042', // Manhattan
destination: '40.7324386599,-74.0341396'
},
routes = new H.map.Group();
async function calculateRoutes(platform) {
var router = await platform.getRoutingService(null, 8);
// The blue route showing a simple truck route
calculateRoute(router, routeRequestParams, {
strokeColor: 'rgba(0, 128, 255, 0.7)',
lineWidth: 10
});
// The green route showing a truck route with a trailer
calculateRoute(router, Object.assign(routeRequestParams, {
'truck[axleCount]': 4,
}), {
strokeColor: 'rgba(25, 150, 10, 0.7)',
lineWidth: 7
});
// The violet route showing a truck route with a trailer
calculateRoute(router, Object.assign(routeRequestParams, {
'truck[axleCount]': 5,
'truck[shippedHazardousGoods]': 'flammable'
}), {
strokeColor: 'rgba(255, 0, 255, 0.7)',
lineWidth: 5
});
}
/**
* Calculates and displays a route.
* #param {Object} params The Routing API request parameters
* #param {H.service.RoutingService} router The service stub for requesting the Routing API
* #param {mapsjs.map.SpatialStyle.Options} style The style of the route to display on the map
*/
async function calculateRoute (router, params, style) {
await router.calculateRoute(params, async function(result) {
await addRouteShapeToMap(style, result.routes[0]);
}, console.error);
}
/**
* Boilerplate map initialization code starts below:
*/
// set up containers for the map + panel
var mapContainer = document.getElementById('map');
// Step 1: initialize communication with the platform
// In your own code, replace variable window.apikey with your own apikey
var platform = new H.service.Platform({
apikey: window.apikey
});
var defaultLayers = platform.createDefaultLayers();
// Step 2: initialize a map - this map is centered over Berlin
var map = new H.Map(mapContainer,
// Set truck restriction layer as a base map
defaultLayers.vector.normal.truck,{
center: {lat: 40.745390, lng: -74.022917},
zoom: 13.2,
pixelRatio: window.devicePixelRatio || 1
});
// add a resize listener to make sure that the map occupies the whole container
window.addEventListener('resize', async () => await map.getViewPort().resize());
// Step 3: make the map interactive
// MapEvents enables the event system
// Behavior implements default interactions for pan/zoom (also on mobile touch environments)
var behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));
map.addObject(routes);
/**
* Creates a H.map.Polyline from the shape of the route and adds it to the map.
* #param {Object} route A route as received from the H.service.RoutingService
*/
function addRouteShapeToMap(style, route){
route.sections.forEach(async (section) => {
// decode LineString from the flexible polyline
let linestring = await H.geo.LineString.fromFlexiblePolyline(section.polyline);
// Create a polyline to display the route:
let polyline = await new H.map.Polyline(linestring, {
style: style
});
// Add the polyline to the map
await routes.addObject(polyline);
// And zoom to its bounding rectangle
await map.getViewModel().setLookAtData({
bounds: routes.getBoundingBox()
});
});
}
// Now use the map as required...
calculateRoutes (platform);

Unfortunately there isn't a good solution for this. It is caused by the way that the page is rendered through the here api. I don't think its fixable.
Having said that you might be able to add some css that would fade in the elements so the transition is smoother.
So you would need to detect the current value of
zoom:
and if it changes you would want to trigger the css animation.
It will take a bit of work and won't be a perfect solution but its better than nothing.

Related

Blazor webassembly: canvas.toDataUrl()?

Using Blazor WebAssembly (3.2.0 preview 5) with BlazorLeaflet to show a map. On this map I want to put an imageOverlay that I want to be able to update more or less often (sometimes not at all and sometimes up to about 1-2 times per second). With a normal js this was quite easily done with the following code snippet (together with Leaflet.Overlay.Rotated plugin) :
Initially:
var map = L.map('mapElement', {
crs: crs,
continuousWorld: true,
center: latlong, // Set center location
zoom: 9, // Set zoom level
minzoom: 0,
maxzoom: 13
});
var overlay = L.imageOverlay.rotated(imageurl, point1, point2, point3, {
opacity: 0.4,
interactive: true,
attribution: "MyUpdatingImage"
});
map.addLayer(overlay);
Then updating the image is simply done by:
var ctx = canvas.getContext("2d");
var imageData = ctx.createImageData(width, height);
//..
// Populating imageData.data with values for r,g,b,a (0-255)
//..
overlay.setUrl(canvas.toDataURL());
I have briefly tested this Blazor Canvas plugin but unfortunately there is no toDataURL() equivalent method/function as far as I can see..
Is there anyone who knows of an alternative method in Blazor WebAssembly?
You can still do this, just use the IJSRuntime service to call a JavaScript method from Blazor WASM and make the update. Example:
#inject IJSRuntime JsRuntime
<canvas id="myMap"></canvas>
#code {
private async Task RefreshMap(){
await JsRuntime.InvokeVoidAsync("myJsMethod", "myNewMapData"); // send any data required
}
}

Creating MapKit JS with origin, destination and waypoints

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.

Showing an infoWindow immediately after a Layer query succeeded

I have a web application, where a user can switch between some 160-ish layers. Most of them are Feature Layers, but some are of type ArcGISDynamicMapServiceLayer.
I need to be able to query those layers the same as I do with FeatureLayers: by clicking on any point on the map and displaying an infowindow.
This is my code so far (removed some bits for clarity):
executeQueryTask: function(evt, scope) {
//"this" is the map object in this context, so we pass in the scope from the caller,
//which will enable us to call the layer and map object and all the other precious widget properties
scope.map.graphics.clear();
scope.map.infoWindow.hide();
//we create a new Circle and set its center at the mappoint. The radius will be 20 meters
//default unit is meters.
var circle = new Circle({
/*...*/
});
// draw the circle to the map:
var circleGraphic = new Graphic(circle, /*...*/));
scope.map.graphics.add(circleGraphic);
var queryTask = new QueryTask(scope.layer.layer.url + "/" + scope.layer.layer.visibleLayers[0]);
var query = new Query();
query.returnGeometry = true;
query.outFields = ["*"];
query.geometry = circle.getExtent();
var infoTemplate = new InfoTemplate().setTitle("");
queryTask.execute(query, function(resultSet) {
array.forEach(resultSet.features, function(feature) {
var graphic = feature;
graphic.setSymbol(/*...*/));
//Set the infoTemplate.
// graphic.setInfoTemplate(infoTemplate);
//Add graphic to the map graphics layer.
scope.map.infoWindow.setContent(graphic.attributes);
scope.map.infoWindow.show(evt.mapPoint, scope.map.getInfoWindowAnchor(evt.screenPoint));
scope.map.graphics.add(graphic);
});
});
},
The key point is insise the queryTask.execute callback. If I uncomment and use graphic.setInfoTemplate(infoTemplate); the result is colored and upon a second click an infoWindow pops up.
There are 2 issues with this approach:
2 clicks are needed
I am unable to click on PolyLines and Points twice, so no infowindow pops up here.
This is why I added a circle to get a 100m buffer in radius to my click. Now I want to immediatly return an infoWindow.
At this point I'm struggeling to successfully create an Info Window, which is immediately displayed.
Currently the line scope.map.infoWindow.setContent(graphic.attributes); throws an error Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.
How can I create that Info Window?
I found a suitable approach, which leaves room for improvements. But this is for another iteration.
//create a new FeatureLayer object
var featureLayer = new FeatureLayer(scope.layer.layer.url + "/" + scope.layer.layer.visibleLayers[0], {
mode: FeatureLayer.MODE_SELECTION,
infoTemplate: new InfoTemplate("Attributes", "${*}"),
outFields: ["*"]
});
//we create a new Circle and set its center at the mappoint. The radius will be 20 meters
//default unit is meters.
var circle = new Circle({/*...*/});
// draw the circle to the map:
var circleGraphic = new Graphic(circle, /*...*/));
scope.map.graphics.add(circleGraphic);
var lQuery = new Query();
lQuery.returnGeometry = true;
lQuery.geometry = circle.getExtent();
featureLayer.queryFeatures(lQuery, function(results) {
array.forEach(results.features, function(feature) {
var graphic = feature;
graphic.setSymbol(/*...*/));
//now that we have the feature, we need to select it
var selectionQuery = new Query();
selectionQuery.geometry = feature.geometry;
featureLayer.selectFeatures(selectionQuery, FeatureLayer.SELECTION_NEW)
.then(function(selectedFeatures) {
console.info("selection complete", selectedFeatures);
if (!selectedFeatures.length) {
return;
}
scope.map.infoWindow.setFeatures(selectedFeatures);
scope.map.infoWindow.show(evt.mapPoint, "upperright");
});
});
});
The change here is, that we are no longer using a QueryTask, but create a new FeatureLayer object in selection mode, using the url and id of the visible layer.
The second noteworthy change is, that we no longer set the content of the infoWindow, but instead set selected features using infoWindow.setFeatures(selectedFeatures). Setting the content of an infoWindow, but not selecting features, hides the action list of the info window, this hinders you to zoom to an object or perform other custom operations.
In addition, this enables you( or me ) to view multiple results in the infoWindow, instead of just one.

Meteor : autorun won't make my code dynamic

I'm working hard on a meteor App which goal is to dynamically display on a google map the path of a vehicle, for example a boat on the sea.
Now, I see this library called gmaps.js, and since it is available on npm (just like google maps api) I decide to use this as a solution to draw of the map.
So, I have one page that add a geographic position (/runs/send) in the database each time I click on a button (this is enougth for testing). Then, on my other page (/runs/show) the goal is to get that data from mongo and prompt it dynamically on the map (meaning, if I add data by pressing the button, I'll see the new path appear on the map). Here is what the code looks like for now :
import { Template } from 'meteor/templating';
import {Tracker} from 'meteor/tracker';
import { Meteor } from 'meteor/meteor'
import gmaps from 'gmaps';
import google_maps from 'google-maps';
import {Mongo} from 'meteor/mongo';
import {Run} from './run.js';
import './methods.js';
Template.runs_show.helpers({
all() {
return Run.find({});
}
});
Template.runs_show.onCreated(function() {
var self = this;
self.autorun(function() {
self.subscribe('allRuns',function() {
google_maps.KEY = "MY API KEY";
google_maps.load(function(google){
console.log("Google Maps loaded");
// this creates a new map
var map = new gmaps({
el: '#map-gmaps',
lat: -12.043333,
lng: -77.028333
});
// for now , the data is on the run with {"maxSpeed" : "75"}
var dada = Run.findOne({"maxSpeed" : "75"});
// path look like [[x1,y1],[x2,y2]]
var path = dada.positions;
// this draws a red line on the map following the points defined in path
map.drawPolyline({
path: path,
strokeColor: '#FC291C',
strokeOpacity: 0.85,
strokeWeight: 6
});
});
});
});
});
So, as you can see, I put my onCreated function in a autorun block, and the data i'm using is from a database, so it's a cursor, so it should be reactive as well.
With a reactive data, inside a reactive block of code (thanks autorun)? expected to see a new line appear on my screen when I press "send" in my second page (this page just add a new set of [x,y] to the run.positions), but.... Nothing ! In fact, If I reload the page manually, the new line appears, of course, but wellll... That's not what I wanted to be honest...
So that's it! any idea what is missing in order to have some true reactivity?
EDIT :
This code works partially : the first time I load the page, the console.log(map) gives a undefined, but I just need to reload once, and then the page will work exactly as intended, showing what I want dynamically. However, one single code reload, and then, again, the console.log(map) gives undefined, and I need a new F5.... Any idea on why it does that / how to solve it?
Template.runs_show.onCreated(function() {
google_maps.KEY = "MY API KEY";
google_maps.load(function(google){
var map = new gmaps({
el: '#map-gmaps',
lat: -12.043333,
lng: -77.028333
});
// with that, I can use the map in the onRendered
Template.runs_show.map = map;
});
console.log(Template.runs_show);
});
Template.runs_show.onRendered(function() {
var self = this;
self.autorun(function() {
self.subscribe('allRuns',function() {
Tracker.autorun(function(){
var map = Template.runs_show.map;
console.log(map);
var dada = Run.findOne({"maxSpeed" : "75"});
var path = dada.positions;
map.drawPolyline({
path: path,
strokeColor: '#FC291C',
strokeOpacity: 0.85,
strokeWeight: 6
});
// seems to not be necesary
//map.refresh();
});
});
});
});
(in this new code, I just create the map in the onCreated, when the gmaps is loaded, and then, I make all the drawing in the onRendered. Btw, I used Template.xxx.map to transmit data between onCreated and onRendered, is that what i'm supposed to do?)
Try using nested templates for this. So that your wrapper template subscribes to data and only renders nested template when subscriptions is ready:
//wrapper template that manages subscription
Template.wrapper.onCreated(function() {
this.subscribe('allRuns');
});
Template.runs_show.onRendered(function() {
google_maps.KEY = "MY API KEY";
google_maps.load(function(google){
var map = new gmaps({
el: '#map-gmaps',
lat: -12.043333,
lng: -77.028333
});
Tracker.autorun(function() {
// Run.find will re-trigger this whole autorun block
// if any of the elements of the collection gets changed or
// element gets created or deleted from collection
// thus making this block reactive to data changes
// this will also work with findOne in case you only want to
// one run only
var runs = Run.find({"maxSpeed" : "75"}).fetch();
// map.clear() or something like that to remove all polylines
// before re-rendering them
runs.forEach(function(run){
map.drawPolyline({
path : path,
strokeColor : '#FC291C',
strokeOpacity : 0.85,
strokeWeight : 6
});
});
});
// ^^ this is pretty dumb and re-renders all items every time
// something more intelligent will only re-render actually
// changed items and don't touch those who didn't change but
// that will require a slightly more complex logic
});
});
wrapper.html:
<template name="wrapper">
{{#if Template.subscriptionsReady }}
{{> runs_show }}
{{/if}}
</template>
P.S. this is mostly pseudo code since I never tested it, so just use it as a guide
Seems the issue is that the subscribe callback is not a reactive context. Try doing what worked for others here and here, as well as putting the tracking in your onRendered.
Template.templateName.onRendered(function() {
var self = this;
self.autorun(function() {
self.subscribe('allRuns',function() {
Tracker.autorun(function(){
...
}
})
})
})

Google Maps v3 - Delete vertex on Polygon

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);
});
}

Categories

Resources