I'm drawing points on a map with OpenLayers like in this example: http://dev.openlayers.org/examples/draw-feature.html
Now I want to know, which area (in meters) is covered by such a drawn point. I know, this depends on the zoom level. And this is also my plan: I want to draw my points with a different size - depending on the zoom level. If the zoom level is maximum, the point should be big. If the zoom level is low, the point should be drawn very small.
Has anyone an idea, how to calculate the point size from pixel to meter?
You could use the map's resolution, which is defined as map units per pixel.
So, assuming your map units are meters, the required pixel size would be:
size_in_meters / map_resolution.
You can use the above calculation in a style map to have points' sizes change dynamically as map resolution changes:
styleMap = new OpenLayers.StyleMap({
'default': new OpenLayers.Style({
pointRadius: "${getSize}"
},
{ context: {
getSize: function(feature) {
return size_in_meters / feature.layer.map.getResolution();
}}
})
});
Maybe you can use a regular polygon (1) instead of the point, with its radius depending on zoom levels.
Then you can call getArea(2) on the obtained geometry.
If you map projection unit is meter, you get it.
1 - http://www.openlayers.org/dev/examples/regular-polygons.html
2 - http://dev.openlayers.org/docs/files/OpenLayers/Geometry-js.html#OpenLayers.Geometry.getArea
HTH,
you can use that constant object to manage your conversions:
ol.proj.METERS_PER_UNIT
/**
* Meters per unit lookup table.
* #const
* #type {Object.<ol.proj.Units, number>}
* #api stable
*/
ol.proj.METERS_PER_UNIT = {};
ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] =
2 * Math.PI * ol.sphere.NORMAL.radius / 360;
ol.proj.METERS_PER_UNIT[ol.proj.Units.FEET] = 0.3048;
ol.proj.METERS_PER_UNIT[ol.proj.Units.METERS] = 1;
And read about it here:
http://openlayers.org/en/v3.1.1/apidoc/proj.js.html
Related
I want to zoom to ol3 map base on scale(for example 1:50000). This means every 1cm in ol map must show 50000cm in real world. for converting scale to resolution I used as follows:
var getResolutionFromScale = function(scale, units) {
var dpi = getDpi();
var mpu = ol.proj.METERS_PER_UNIT[units];
var inchesPerMeter = 39.37;
return parseFloat(scale) / (mpu * inchesPerMeter * dpi);
}
function getDPI()
{
var div = document.createElement("div");
div.style.width="1in";
var body = document.getElementsByTagName("body")[0];
body.appendChild(div);
var dpi = document.defaultView.getComputedStyle(div, null).getPropertyValue('width');
body.removeChild(div);
return parseFloat(dpi);
}
Now for testing it, I use ScaleLine and when scaleLine controle show 1000m, it's length must be 2cm, but it is almost 2.5cm.
Where is th problem? How can I zoom base on scale?
Online Demo
The projection is a Mercator projection that preserve angles but not the distance. This means that the scale is not the same on the whole map :(
To see it, just move from equator to the pole and see the ScaleLine length growing.
Thus you just can't calculate the scale worldwide, you have to calculate it locally (calculate a distance between 2 points at the center of the view in the map projection and the haversine distance to get the ratio). Be aware the DPI depends on the device (screen, printer) your are using and may be quite different form one to another.
You can use the geometry.getLength() and ol.Sphere.getLength() to compute the 2 distances.
Look at the ol example: https://openlayers.org/en/latest/examples/measure.html
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.
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.
I render a map with a route from point A to point B and zoom to the bounding box of the route.
I also render an associated information bubble for point B (destination).
This specific portion of my application does not involve user intervention so it is completely unattended
The map is also printed
Point B is always different
using Enterprise Javascript API v2.5.4
My issue is that many times the info bubble overlays the route polyline which obstructs the route.
I see InfoBubble alignment options for left, right, above, below but these will not help as points are dynamic
Any suggestions on positioning of Info Bubble to assure it does not overlay route?
The defaultXAlignment and defaultYAlignment properties can be used to set a preferred position of the InfoBubble.
You can use the defaultXAlignment and defaultYAlignment properties to alter the preferred offset for the Infobubble e.g.:
var infoBubbles = new nokia.maps.map.component.InfoBubbles();
infoBubbles.options.set("defaultXAlignment",
nokia.maps.map.component.InfoBubbles.ALIGNMENT_RIGHT);
infoBubbles.options.set("defaultYAlignment",
nokia.maps.map.component.InfoBubbles.ALIGNMENT_ABOVE);
map.components.add(infoBubbles);
In your case I would suggest the following.
When you zoom to the bounding box of the route add a padding factor to ensure there is space to display the infoBubble. The simplest way would be to zoom out one more stop, but you could use the padding property of the nokia.maps.map.Display to ensure you have enough space when you zoom(). See
nokia.maps.map.Display.setPadding()
Secondly calculate the bearing between the start and endpoints:
Number.prototype.toRad = function() {
// convert degrees to radians
return this * Math.PI / 180;
}
function bearing(lat1, lon1, lat2, lon2) {
lat1 = lat1.toRad(); lat2 = lat2.toRad();
var dLon = (lon2-lon1).toRad();
var y = Math.sin(dLon) * Math.cos(lat2);
var x = Math.cos(lat1) * Math.sin(lat2) -
Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon);
return Math.atan2(y, x);
}
If the result is between 0 and Math.PI/2 you are travelling North-East fix the Infobubble to display on the right and above.
If the result is between Math.PI/2 and Math.PI you are travelling South-East fix the Infobubble to display on the right and below.
If the result is between -Math.PI and -Math.PI/2 you are travelling South-West fix the Infobubble to display on the left and below.
If the result is between -Math.PI/2 and 0 you are travelling North-West fix the Infobubble to display on the left and above.
You can find more information about completely fixing an InfoBubble the answer here
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.