Nokia HERE Maps canvas drawing speed - javascript

I'm trying to draw a grid of rectangles on top of the map tiles using the Javascript API and highlight (switch fillColor for) whichever rectangle is currently under the mouse pointer. I would expect such a small change to be effective almost immediately.
However the speed at which changes take place is unbearable for something like this, as changes seem to trigger with a delay of maybe 100ms or so. This applies even if I save a reference to one of the rectangles on a 2x2 grid and then change its color from the console. So this seems unlikely (but still possible) to be a performance issue but rather feels like the Maps simply won't refresh often enough.
Is there maybe a way for me to tell the Maps to redraw a region immediately, or should I use some other way of drawing which would be more performant? I currently have a workaround of using a floating div as the highlight, but it feels a bit wrong and comes with other issues to hack around.
rect = new nokia.maps.map.Rectangle(boundingBox, opts)
...
// slow, but not a deal breaker
map.objects.add(rect)
...
// too slow to happen on every mouseenter/mouseleave event
rect.set('fillColor', '000000')
I'm using the 2.5 version of the Javascript API and I'm targeting mostly Chrome.

You could try map.update(-1, true); to force a redraw the map.
Alternatively, one possible performance improvement (which has several caveats) would be to use an overlay for the grid and only one rectangle. This could be of use if you are trying to highlight the current square region of a map tile as served from the TMS server.
You could add a 256x256 grid (or 128x128 or 64x64 etc) using the code in the question here, and then merely move one rectangle over the map to show the current highlight:
For a given zoom and coordinate, the current tile CoordinateZoomToXY is:
var longitude = coord.longitude,
latitude = coord.latitude,
tilesPerRow = Math.pow(2, zoom),
column,
row;
longitude /= 360;
longitude += 0.5;
latitude = 0.5 - ((Math.log(Math.tan((Math.PI / 4) + (latitude * Math.PI / 360))) / Math.PI) / 2.0);
column = Math.floor(longitude * tilesPerRow);
row = Math.floor(latitude * tilesPerRow);
hence the reverse operation (XYZtoCoordinate) is:
var tilesPerRow = Math.pow(2, zoom),
longitude = column / tilesPerRow * 360.0 - 180.0,
lat_rad = Math.atan(sinh(Math.PI * (1 - 2 * row / tilesPerRow))),
latitude = lat_rad * 180.0 / Math.PI;
and the current tile is:
nokia.maps.geo.BoundingBox.coverAll([
XYZtoCoordinate(zoom, column , row),
XYZtoCoordinate(zoom, column + 1, row + 1)]));
If you added this to the listener and just moved one rectangle, it may help as you would only need to update one map object each time.

Related

React-Leaflet : Calculate actual camera height from the ground based on zoom level

Is it possible to get the height of the leaflet's 'camera' from the ground, based on the zoom level?
If you look at the screenshot above (taken from a movie), I am trying to achieve the same result in the red circle, which is displaying at what height the 'camera' is at on the current zoom level.
I've tried to search for a solution for this, but couldn't find any. Keywords used such as 'height', 'zoom level', 'elevation' yielded results that were either to calculate the height of the map canvas, or the topographic elevation.
What I am looking for is how to calculate the hypothetical height from the ground to where the 'camera' (or users viewing the map). My thinking is that this can somehow be achieved via referencing to the zoom level.
Is there a way to achieve this using react-leaflet?
Thank you #SethLutske for pointing to the right direction. The actual answer can be found on the gis.stackexchange : link
const EARTH_RADIUS_IN_METERS = 6371010
const TILE_SIZE = 256
const SCREEN_PIXEL_HEIGHT = 768
const RADIUS_X_PIXEL_HEIGHT = 27.3611 * EARTH_RADIUS_IN_METERS * SCREEN_PIXEL_HEIGHT
const altitude = (zoom, latitude) => (RADIUS_X_PIXEL_HEIGHT * Math.cos((latitude * Math.PI) / 180)) / (Math.pow(2, zoom) * TILE_SIZE)
There's actually a lot of discussion related to the topic in the gis.stackexchange link above, but I choose this, as rightfully pointed out by #sethluske and it worked well with react-leaflet.

How can I prevent Leaflet from splitting on what appears to be 180 longitude? [duplicate]

Is there a way to get a shape to wrap from one edge across the dateline meridian (180° longitude) to appear on the other side of the map in Leaflet.js?
I've looked at:
http://leafletjs.com/reference.html#latlng-wrap
https://github.com/Leaflet/Leaflet/issues/82
But I'm unsure on what I could do to get it to reliably draw across the dateline...
Thank you in advance!
Oh, you're hitting antimeridian artifacts. You're not the first one, and will not be the last one.
In Leaflet, there are basically two approaches for this problem:
1a: Cut the polygon beforehand
If you know your GIS tools, preprocess your polygon, so you end up with two (or possibly more) polygons. See «How can I make a polyline wrap around the world?».
Once you have a file with several polygons which don't cross the antimeridian, they should render fine. You will hit artifacts (namely, a vertical polygon border at the antimeridian, spanning the inside ofthe polygon) if you apply a border to the polygons, so you might want to cut a polygon and a polyline with the polygon's edge if you want to render both nicely.
1b: Cut the polygon on the browser
If you don't want to cut the polygon beforehand, you can let the web browser do it on the fly.
There are some utilities that can help here, but I'm going to point to Leaflet.VectorGrid in particular. By leveraging geojson-vt, it can cut polygons and their edges into tile-sized polygons and polygon edges. It can handle geometries crossing the antimeridian quite well.
You might want to look into geojson-vt directly, or maybe turf.js to do some on-the-fly geoprocessing.
2: Think outside the [-180..180] range
Leaflet can handle longitudes outside the [-180..180] range. In Leaflet, longitudes wrap only the TileLayer's tiles and not markers or polylines.
In other words: a marker at [0, -179] is shown at a different place than [0, 181]. See this answer for an example.
In other words: a line from [0, 179] to [0, -179] is 358 degrees long, but a line from [0, 179] to [0, 181] is two degrees long.
In other words: you can have linestrings or polygons with coordinates with longitudes outside the [-180..180] range, and that's fine for Leaflet. It's not fine for a lot of GIS software (in fact, I think that the new GeoJSON spec prohibits it). But it will make Leaflet happy.
When you are working with a cylindrical projection, as Leaflet does, it can be solved relatively easily with trigonometry. My solution is based on the first approach of Ivan's answer above, which is cutting the line in two parts at the 180th meridian. My solution is not perfect, as I will show below, but it is a good start.
Here is the code:
function addLineToMap(start, end) {
if (Math.abs(start[1] - end[1]) > 180.0) {
const start_dist_to_antimeridian = start[1] > 0 ? 180 - start[1] : 180 + start[1];
const end_dist_to_antimeridian = end[1] > 0 ? 180 - end[1] : 180 + end[1];
const lat_difference = Math.abs(start[0] - end[0]);
const alpha_angle = Math.atan(lat_difference / (start_dist_to_antimeridian + end_dist_to_antimeridian)) * (180 / Math.PI) * (start[1] > 0 ? 1 : -1);
const lat_diff_at_antimeridian = Math.tan(alpha_angle * Math.PI / 180) * start_dist_to_antimeridian;
const intersection_lat = start[0] + lat_diff_at_antimeridian;
const first_line_end = [intersection_lat, start[1] > 0 ? 180 : -180];
const second_line_start = [intersection_lat, end[1] > 0 ? 180 : -180];
L.polyline([start, first_line_end]).addTo(map);
L.polyline([second_line_start, end]).addTo(map);
} else {
L.polyline([start, end]).addTo(map);
}
}
This will calculate the latitude where the line crosses the 180th meridian, and draw the first line from the starting point to this latitude on the 180th meridian, and then a second one from this point to the end.
The picture below shows an example of the result.
Even though I'm fairly certain the math checks out on my calculations, there is a small kink where the two lines are separated. I'm not sure whether this is due to the rendering of the Leaflet map, or an actual error in my calculations.
The starting point is [35.552299, 139.779999] and the end point is [64.81510162, -147.8560028].
The total longitudinal difference between the points is 72.364, and latitudinal difference is 29.263. Using the code below or an online calculator, the angle α is 22.018. Taking only the distance from the starting point to the 180th meridian, and the angle α, the latitudinal difference between starting point and intersection is 16.264. Adding the latitude of the starting point and this value, we get a latitude of 51.8166 at the 180th meridian. Drawing a straight line on a map tells me that this value should be slightly higher up, but I can't figure out why or how that is calculated.
If you want a curved line that accurately shows the curvate of the earth, I would highly recommend using Leaflet.Geodisic. It is easy to use and has a solution to the antimeridian problem built-in so you don't have to worry about it.
If you're using react-leaflet, the easiest way is to use it together with leaflet.geodesic and set the lines with leaflet.geodesic.
Import the required libraries
import { GeodesicLine } from "leaflet.geodesic"
import * as L from "leaflet"
import {
MapContainer,
} from "react-leaflet"
Some points to join up across the meridian
const East = new L.LatLng(-41.75412, 175.70595)
const FurthurEast = new L.LatLng(-42.43624, -178.65339)
const Chile = new L.LatLng(-26.74165, -71.41818)
In the return of your component:
<MapContainer
center={centerMarker as [number, number]}
zoom={5}
whenCreated={(mapInstance) => {
new GeodesicLine([East, FurthurEast, Chile], {
weight: 10,
color: "red",
}).addTo(mapInstance)
}}
>
{...children}
</MapContainer>

Diameter or drawing way off when zoom is >13 on Google Maps

I'm writing some drawing tools for Google Maps where a user selects a tool and clicks and drags to get a distance. Here's a gif of what the "ruler" tool looks like:
I made a rectangle one too and that works perfect as well. I'm having issues though with a Circle tool in calculating the diameter or radius of the circle once the zoom level is greater than 13. You can see the distance in the gifs below. The first one is zoom level 13, next is 14.
Here's the code I have:
var diameter = drawingManager.distanceBetweenTwoLatLng(
this._startPosition,
drawingManager.fromEventToLatLng(event)
);
this.circle.setOptions({
// After level 14 zoom we don't multiply *1000. *1000 is a Magic Number™
// and I have no idea why I need it or why zoom level 14 needs to not
// have it but 13 and does.
radius: map.getZoom() > 13 ? diameter : diameter * 1000
});
I calculate the pixels to LatLng with this (and where I think it might be failing because it gets the scale?):
var map = this.settings.map;
var projection = map.getProjection();
var topRight = projection.fromLatLngToPoint(map.getBounds().getNorthEast());
var bottomLeft = projection.fromLatLngToPoint(map.getBounds().getSouthWest());
var scale = 1 << map.getZoom();
return projection.fromPointToLatLng(new google.maps.Point(x / scale + bottomLeft.x, y / scale + topRight.y));
The problem is the drawingManager methods shown above (distanceBetweenTwoLatLng and fromEventToLatLng) work totally fine with all the other tools zoomed at any level.
You can see my current workaround is just checking for the zoom level and giving it different radius settings.
After more debugging my coworker pointed out that maybe the distance scale was off. I had assumed (incorrectly) that the distance scale needed to be what google maps is set to. So if the user has it on miles it would be miles, for example. I went to their docs and saw it needed to be in meters. Seems so obvious now :\ Anyway, my issue was that i was using the user's current distance scale rather than m which is what Google's Circle shape uses exclusively.

How to implement a repulsion between 2D circles (paperjs)

I am making a paperjs app where you have circles and each circle can move freely. Some circles are connected to each other by means of lines which would cause the circles to come nearer to one another - ie the line simulates an elastic band between circles. However the circles are not allowed to overlap, so I want to make some kind of collision repulsion. Currently I have implemented this as a repulsive force between circles. For each circle I check the location of all other circles, and each circle's velocity vector is increased in the opposite direction to the circle close to it, in proportion to how close it is to this one. So in effect something like velocityvector += -(vectorFromThereToHere / 10)
However this has the effect that between the attractive force between connected circles and the repulsive force between all circles, you end up with a continual back and forth jittering.
What then would be the best way to implement some kind of repulsion between circles that wouldn't cause any juddering but would simply allow the circles' edges to touch one another whilst not coming any closer together? In effect I want the circles to simply bump into each other, not be allowed to slide over one another, but they are allowed to slide along each other's outside edge frictionlessly to get to where their momentum carries them.
You could implement an inelastic collision, followed by a position-fixing step. The idea is to apply an impulse on the objects in the direction of the normal of the impact.
// vx: velocity vector of object x
// ux: velocity vector of object x after impact
// mx: mass of the object x (1 if all objects are the same size)
// n: normal of impact (i.e.: p1-p2 in the case of circles)
// I: the coefficient of the impulse
// Equation of an inelastic collision
u1 * n = u2 * n
// Equations of the velocities after the impact
u1 = v1 + I * n / m1
u2 = v2 - I * n / m2
// solved for I:
I = (v1 - v2) * n / ((n*n)*(1/m1 + 1/m2))
When you have I you just have to apply the velocity changes. You might as well check if I > 0 before applying the impulses, to prevent the shapes stick together. Let's see how it works, and add position iterations if the balls start to overlap slowly after all these anyway.
PS: You might repeat the whole collision step in a single timeframe as well, to get better results when objects are involved in many collisions (because they are stuck together in a big ball)

How to convert radius in metres to pixels in mapbox leaflet?

I am working on an application where I am trying to run some algorithms on a map and calculate coords but they are slightly off because I am running the calculations on latitude and longitudes and the final results are getting distorted.
Now I am trying to convert all coordinates to EPSG3857 Web Mercator coordinates like so:
var crs = L.CRS.EPSG3857;
var zoom = terrainAnalysisMap.getZoom();
markerToPoint = crs.latLngToPoint(marker.getLatLng(), zoom);
markerOnRadiusToPoint = crs.latLngToPoint(markerOnRadius.getLatLng(), zoom);
Now I also have a radius which I will have to convert from metres to pixels but I cannot figure out how. How much is 1 metre in pixels? I mean it would depend on the zoom level too, right? So how to go about converting radius to pixels in mapbox and leaflet?
If you happen to have the latitude of the place where you are looking to convert the radius to pixels, you can make use of the "Meters per pixel" function described at http://wiki.openstreetmap.org/wiki/Zoom_levels. Here's my javascript implementation:
var metersPerPixel = function(latitude, zoomLevel) {
var earthCircumference = 40075017;
var latitudeRadians = latitude * (Math.PI/180);
return earthCircumference * Math.cos(latitudeRadians) / Math.pow(2, zoomLevel + 8);
};
var pixelValue = function(latitude, meters, zoomLevel) {
return meters / metersPerPixel(latitude, zoomLevel);
};
A meter is a meter, no matter what your zoom level is. What you need is to convert a meter into degrees since long/lat is a polar coordinate system. With the circumference of Earth being 40,075 km, you get 0.00000898315 deg/m which you need to multiply with the size of the object (1 m) to get the degrees which you have to add to your coordinate to get a point which intersects with the radius of the circle that you want to draw.
But usually, it's easier to just draw a circle with a radius of 10 px around the center coordinate (after you transformed it from world to screen) making sure that the circle is always the same size, no matter of the zoom level. That way, people won't have a problem to see and/or click it.
[EDIT] Your question is related to Parametric equation to place a leaflet marker on the circumference of a circle is not precise?
My suggestion is to forget about world coordinates for the drag/drop problem. Obviously, you already have a circle (which means you must know the center point and the radius in pixels - otherwise, you couldn't draw it).
What you need is to implement the dragging of the marker only in screen coordinates. That should be pretty simple to implement.
When the user releases the mouse, all you have to do is to take the screen coordinate and convert it into long/lat once.
One problem to keep in mind: If you're using something like Mercator projection, the coordinates will be off as you get closer to the poles. To solve this, you need to work with an ellipse (wider than tall) instead of a circle.
I have done this once using this array for pixel/realworld-meter translation
pixelMeterSizes: {
10: 1 / 10,
11: 1 / 9,
12: 1 / 8,
13: 1 / 7,
14: 1/ 5,
15: 1 / 4.773,
16: 1 / 2.387,
17: 1 / 1.193,
18: 1 / 0.596,
19: 1 / 0.298
}
Notice, that above a zoomlevel of 15, i simplified things, because the symbols were getting too small and would not be visible anymore.
I used this as a basic reference http://wiki.openstreetmap.org/wiki/Zoom_levels.
This worked quite good for me, but i am not sure what will happen when dealing with high res displays and such. Guess it could fail in those scenarios.

Categories

Resources