How zoom base on scale in OpenLayers 4? - javascript

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

Related

Convert 2D shape into 3D in d3.js and adjust height according to the value in ANGULAR

I am using d3.js v6 to create a 3D graph of the below 2D chart representation. This circle has multiple squares in it and each square has been assigned a color based on the value. The bigger the value, more darker the square.
Now I want to convert this in 3D shape where only the height of a particular square increases when the value gets high, so the result would be somehow similar to the image below. The base would be circular but the height of each value would go up based on the value
I am trying to achieve this in angular, if anyone could please help me out. Here is the Stackblitz Link
I made the one as you requested.
source code on github
here's working demo: https://stackoverflow-angular-3d-chart.surge.sh/
This involved several intricate steps.
I couldn't go any deeper from this answer because every part that I mentioned here could be hours worth tutorial. These are what I've felt interesting when I was working on it.
Used Stacks
EDIT: the stackblitz code is now outdated. I've used the most recent version for each package.
Three.js r143
D3.js v7.6.1
Angular.js v14
Getting Circle Grid
experiment note on ObservableHQ: https://observablehq.com/#rabelais/circle-inside-grids
First I've experimented on SVG with D3.js to get proper circle grid.
It seemed daunting but turned out very simple. I've slightly modified Midpoint circle algorithm to fill box grids in circular shape. It is little different from filling grids in 3d space; 2d space has top left corner as beginning of everything. In 3d space, everything starts from center.
const midPointX = gridWidth / 2;
const midPointY = gridHeight / 2;
const { midPointX, midPointY, radius } = config;
const getCollision = ({ x, y }) => {
return (midPointX - x) ** 2 + (midPointY - y) ** 2 - radius ** 2 > 0;
}
Calculating Gaps
d3's scale band supports automatic calculation of gaps and content size in responsive environment.
const scaleBandX = d3
.scaleBand()
.domain(d3.range(0, config.gridWidth))
.range([config.margin, config.svgWidth - config.margin * 2])
.paddingInner(0.2);
const scaleBandY = d3
.scaleBand()
.domain(d3.range(0, config.gridHeight))
.range([config.margin, config.svgHeight - config.margin * 2])
.paddingInner(0.2);
scaleBandX.bandwidth(); // width of box in 2d space
scaleBandY.bandwidth(); // height of box in 2d space
scaleBandX(boxIndex); // x position of box in 2d space with gap
scaleBandY(boxIndex); // y position of box in 2d space with gap
as D3 assumes vector calculation as normal, it was pretty easy to apply the very same method in 3D.
Expressing on 3D space
I've used Three.js to express everything in 3D. The app is running on Angular per request but it does not matter which frontend framework is used.
Everything about expressing 2d bar chart on 3d is very trivial. However, the dimension is different from 2d; the positions have to be swapped.
// code to make a single bar mesh
makeBar(d: typeof gridData[0]) {
// length and height is swapped. because camera is looking from 90 degree angle by default.
const geo = new T.BoxGeometry(d.w, d.l, d.h, 32, 32);
const mat = new T.MeshPhysicalMaterial({ color: 'red' });
const mesh = new T.Mesh(geo, mat);
mesh.position.x = d.x;
// z and y is also swapped. because of the same reason.
mesh.position.z = d.y;
mesh.position.y = d.z;
return mesh;
}
then each element is assigned as 3d Group, to make them centered altogether.
EDIT: color scheme was missing. it is now added.

D3 - Project a single XY coordinate (Equirectangular) into a Orthographic coordinate

I'm trying to project a screen/pixel coordinate from an Equirectangular image into an Orthographic coordinate using D3 and it's projection tools (which I hardly understand).
I've found an example that shows how to do this using an interpolation animation to show the results but I can't understand how to achieve the same effect without rendering any canvas/svg while projecting a single coordinate.
https://observablehq.com/#d3/orthographic-to-equirectangular
All I need is to use the calculation D3 does to such coordinates to fix the distortion generated by the Equirectangular image in a different process, where I loop for each pixel in the equirectangular image to convert it into an "undistorted" one.
Here's a picture of what I'm trying to achieve.
My code looks something like this:
// Equirectangular image source size
var width = $("#equirectangular_image").width(); // 1024
var height = $("#equirectangular_image").height(); // 512
// Screen XY into LATLON - 512 and 256 are just an example representing the center of the image.
// This was wrong
// var offsetX = 512 / width * 360;
// var offsetY = 256 / height * 180;
// Edited
var offsetX = 512;
var offsetY = 256;
var projectionOrtho = d3.geoOrthographic();
var projectionEqui = d3.geoEquirectangular();
var ortho_coords = projectionOrtho([offsetX,offsetY]);
var fixed_coords = projectionEqui(ortho_coords);
// fixed_coords isn't returning the expected coordinates
Please, help! Thanks!
PS: I'm looking for a solution to the main topic which is to fix the distortion generated by the equirectangular image. If there's a better approach to do this without using any external libraries I'd be glad to go down that road.

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.

a failed attempt at placing a dot over an image based on known lat and lng points

I apologize for the vague title, I really can't figure out a batter way to sum it up. Suggestions are more than welcome.
I'm working on a project that really doesn't NEED a google map, it'd just create extra overhead for this project. But, I can't figure out how to do this WITHOUT google maps... so far.
If I were to place a graphical overlay of a floor plan into google maps, I could use a browser location to approximate a users position. For this, it's a VERY large place, so there's room for some poor accuracy.
Now what I'm trying to do is knowing the bounding of that overlay, place the image into a div, then calculate the users position in the div based on their browser lat/lng.
I'm able to get close using an image that is square in that the top of the sides are horizontal and vertical, because that crates my area. But since none of the world works like that, I need an area that isn't square to appear squared. I'm really struggling on the math.
Here's what I'm trying to do!
And here's a link that tests my concept, but doesn't account for an image that needs to be rotated on a map: http://www.freeptools.com/mapster/singlerun/maptest.php
Like I said, I'm sure that this can be done, but I haven't been able to figure out the math for it just yet. It's driving me crazy.
Here's my code that's doing the magic. A function gets the browser coordinates and sends them to the initialize function. Then based on mapping it myself earlier, I have the bounds of the image I'm trying to map. I'm using the haversine formuls to try to get the height and width at the points of the user (since the map curves, it'll be inaccurate anywhere else) position, then the distance from the top and left most points, then taking the distance from the left/top and dividing it by the width/height to get a percentage of how far they are from the left/top to position the dot. This idea, while I can't get it to be super accurate, works in this example, because the image lines up horizontally on a map. My problem is how do I calculate those distances when the image is rotated on a map? I can't figure out that math.
function initialize(y3, x3) { //lat, lon
//var overlayBounds = new google.maps.LatLngBounds(new google.maps.LatLng(41.11246878918085, -90.5712890625), new google.maps.LatLng(47.68652571374621, -82.001953125));
var box = $("#map_canvas");
//image bounds
var x1 = -82.001953125;
var x2 = -90.5712890625;
var y1 = 41.11246878918085;
var y2 = 47.68652571374621;
var boxWidth = x2 - x1;
var boxHeight = y2 - y1;
//now we need to figure out where this rests, first we get the percentage
//var posLeft = haversine(y3, x1, y3, x3); //(x3 - x1);
var posLeft = (x3 - x1);
var posLeftPct = (posLeft/boxWidth)*100;
//var posTop = haversine(y2, x3, y3, x3); //(y2 - y3);
var posTop = (y2 - y3);
var posTopPct = (posTop/boxHeight)*100;
box.append('<img src="http://www.freeptools.com/mapster/icons/icon23.png" style="position:absolute; z-index:200; right:'+posLeftPct+'%; top:'+posTopPct+'%">');
}
Assuming the area is a parallelogram, you'll need to know 3 of the vertices of the area and the width/height of the area where you want to draw the pin(e.g. the floorplan-image).
The following will use the Geo-and LatLon-libraries from http://www.movable-type.co.uk/scripts/latlong.html
An image for better understanding:
initially calculate some values:
bearings(nw to sw and nw to ne) via LatLon.bearingTo
distances(sw to nw and ne to nw) via LatLon.distanceTo
now calculate the intersections(marked as intersection-x and intersection-y in the image) via LatLon.intersection
The calculation would be:
intersection-x:
LatLon.intersection(ne, bearingNWtoSW, target, bearingNWtoNE)
intersection-y:
LatLon.intersection(sw, bearingNWtoNE, target,bearingNWtoSW)
now calculate percentual values for the distance of border-north to target and border-west to target:
border-north to target:
((distanceNWtoNE-(target.distanceTo(intersection-x)))*100)/distanceNWtoNE
border-west to target:
((distanceNWtoSW-(target.distanceTo(intersection-y)))*100)/distanceNWtoSW
Finally calculate the coordinates based on the width/height of the given floorplan and the results of the previous calculation
x=((width*distanceBorderNorthToTargetInPercent)/100);
y=((height*distanceBorderWestToTargetInPercent)/100);
A method(extension of the mentioned LatLon-library) that performs all these calculations:
/**
*#param w int width of the drawing
*#param h int height of the drawing
*#param sw object LatLon of southwest of the drawing
*#param nw object LatLon of northwest of the drawing
*#param ne object LatLon of northeast of the drawing
*#return mixed object with x/y-coordinates or null when LatLon is outside of the area
**/
LatLon.prototype.translate = function(w,h,sw,nw,ne) {
var x = {distance:nw.distanceTo(ne),bearing:nw.bearingTo(ne)},
y = {distance:nw.distanceTo(sw),bearing:nw.bearingTo(sw)},
intersectionY = LatLon.intersection(sw, x.bearing, this, y.bearing),
intersectionX = LatLon.intersection(ne, y.bearing, this, x.bearing),
distanceX,distanceY;
if(intersectionX && intersectionY){
distanceX=((x.distance-(this.distanceTo(intersectionX)))*100)/x.distance,
distanceY=((y.distance-(this.distanceTo(intersectionY)))*100)/y.distance;
return {x:((w*distanceX)/100),y:((h*distanceY)/100)};
}
return null;
};
Demo:http://jsfiddle.net/doktormolle/nsbqpcvg/embedded/result/
Hover the highlighted floorplan on the google-map to see the result of the calculation(the google-Maps-API is only used for the demonstration, the calculation will be done without using the Maps-API)

Get radius size in meters of a drawn point

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

Categories

Resources