How can I convert a google maps circle into GeoJSON? - javascript

I need to convert a google maps Circle into GeoJSON. GeoJSON doesn't support circles so I want to generate an N-sided polygon. I found a nice method for generating a regular N-sided polygon but that requires the radius to be defined in the coordinate system while the radius of a circle in Google Maps is defined in meters.

Google Maps has a handy set of spherical functions in the geometry library that makes this really easy for us. Specifically, we want the computeOffset function:
Returns the LatLng resulting from moving a distance from an origin in the specified heading (expressed in degrees clockwise from north).
We have the origin (the center of the circle) and a distance (the radius of the circle), so we just need to calculate a set of headings for the points based on how many sides we want.
function generateGeoJSONCircle(center, radius, numSides){
var points = [],
degreeStep = 360 / numSides;
for(var i = 0; i < numSides; i++){
var gpos = google.maps.geometry.spherical.computeOffset(center, radius, degreeStep * i);
points.push([gpos.lng(), gpos.lat()]);
};
// Duplicate the last point to close the geojson ring
points.push(points[0]);
return {
type: 'Polygon',
coordinates: [ points ]
};
}
The geometry library is not included by default. You must specifically ask for it via the libraries parameter.

Related

Inverted Y axis of custom tile images names

There is a know problem of Leaflet that when you use a custom tile provider, not with real earth images, set crs: L.CRS.Simple, Leaflet queries for images where Y coordinate is inverted in comparison to the math axis. So the first top-right image's location is 1x-1 instead of 1x1.
In the internet topics about inverting Y axis are rather old, so my question is: nowadays is there a normal short and built-in way to invert queried Y axis?
The only old solutions I've found were rewriting Leaflet internal objects, like extending L.CRS.Simple.
As noted in the Leaflet tutorial for WMS/TMS, the canonical way of inverting the Y coordinate for tile coordinates is using {-y} instead of {y} in the tile URL template. e.g.:
var layer = L.tileLayer('http://base_url/tms/1.0.0/tileset/{z}/{x}/{-y}.png');
Note, however, that (as of Leaflet 1.3.1) that only works for maps with a non-infinite coordinate system.
In your case, you might want to get around this by creating your own subclass of L.TileLayer. There is a complete guide on the Leaflet tutorial about extending layers, but the TL;DR version for a tilelayer that shifts its tile coordinates is:
L.TileLayer.CustomCoords = L.TileLayer.extend({
getTileUrl: function(tilecoords) {
tilecoords.x = tilecoords.x + 4;
tilecoords.y = tilecoords.y - 8;
tilecoords.z = tilecoords.z + 1;
return L.TileLayer.prototype.getTileUrl.call(this, tilecoords);
}
});
var layer = new L.TileLayer.CustomCoords(....);
And the specific case for just inverting the Y coordinate is:
L.TileLayer.InvertedY = L.TileLayer.extend({
getTileUrl: function(tilecoords) {
tilecoords.y = -tilecoords.y;
return L.TileLayer.prototype.getTileUrl.call(this, tilecoords);
}
});
var layer = new L.TileLayer.InvertedY(....);

How to use Leaflet flyTo() with unproject() and GeoJSON data on a large raster image?

I'm building a story map with Leaflet using a large image sliced into tiles rather than 'real world' map data. I'm using this plugin: https://commenthol.github.io/leaflet-rastercoords/ and this repo: https://github.com/jackdougherty/leaflet-storymap
Loading my GeoJSON data and unprojecting the coordinates correctly plots them on my image map:
$.getJSON('map.geojson', function(data) {
var geojson = L.geoJson(data, {
// correctly map the geojson coordinates on the image
coordsToLatLng: function (coords) {
return rc.unproject(coords)
},
But when I get to onEachFeature, I hit the wall with map.flyTo(), which is calling geometry.coordinates from my JSON file, but not unprojecting them so flyTo() is interpreting them as geospatial coordinates way off the map:
map.flyTo([feature.geometry.coordinates[1], feature.geometry.coordinates[0] ], feature.properties['zoom']);
I tried passing the unprojected coordinates to variables and then to map.flyTo() and variations on nesting functions, such as map.flyTo.unproject(..., but no luck.
How do I pass my raster coordinates to flyTo()?
I'm not only new to Leaflet, but new to JavaScript. I hacked my way this far, but I'm stumped. I'm sure the solution is obvious. Any help is greatly appreciated.
In your case you would probably just need to use rc.unproject to convert your coordinates into LatLng that you can pass to flyTo:
map.flyTo(
rc.unproject(feature.geometry.coordinates),
feature.properties['zoom']
);
That being said, I must admit I do not exactly see the point of using leaflet-rastercoords plugin, since you can easily do the same by following the Leaflet tutorial "Non-geographical maps".
var yx = L.latLng;
var xy = function(x, y) {
if (L.Util.isArray(x)) { // When doing xy([x, y]);
return yx(x[1], x[0]);
}
return yx(y, x); // When doing xy(x, y);
};
With this, whenever you want to convert your "raster" coordinates into something usable by Leaflet, you would just use xy(x, y) with x being your horizontal value, and y your vertical one.
The added benefit is that many other things will become easily compatible.
Since you use tiles instead of a single image (that is stretched with ImageOverlay in the tutorial in order to fit the coordinates), you should modify the CRS transformation, so that at zoom 0, your tile 0/0/0 fits your entire coordinates. See also Leaflet custom coordinates on image
I.e. in the case of leaflet-rastercoords example:
Original raster image size: 3831 px width x 3101 px height
Tiles size: 256 x 256 px
Vertical "raster" coordinates are increasing while going down (whereas in the Leaflet tutorial, they increase going up, like latitude).
Tile 0/0/0 actually covers more than the original image, as if the latter were 4096 x 4096 px (the rest is filled with white)
Determination of the new CRS:
Tile 0/0/0 should cover coordinates from top-left [0, 0] to bottom-right [4096, 4096] (i.e. 256 * 2^4 = 256 * 16 = 4096) => transformation coefficients a and c should be 1/16
No offset needed => offsets b and d are 0
No reversion of y vertical coordinate => c is positive
Therefore the new CRS to be used would be:
L.CRS.MySimple = L.extend({}, L.CRS.Simple, {
// coefficients: a b c d
transformation: new L.Transformation(1 / 16, 0, 1 / 16, 0)
});
Now your flyTo is very similar, but many other things are also compatible:
map.flyTo(
xy(feature.geometry.coordinates),
feature.properties['zoom']
);
Demo adapted from leaflet-rastercoords example, and using an extra plugin to demonstrate compatibility: https://plnkr.co/edit/Gvei5I0S9yEo6fCYXPuy?p=preview

Get new point Coordinate by Current Point and Angle and Distance in arcgis js

I'm developing a Web GIS Application using arcgis javascript Api and I need to drawing line from point1 by distance and angle. first step calculate point2 by this formula
point2X = point1.x + distance * Math.cos(angle)
point2Y = point1.y + distance * Math.sin(angle)
Distance unit 'Meter' and angle in 'radians'
Second Step: Draw line with point1 and point2. If the input distance is 1000 meters, the drawn line shows the length as 866 meters, when measured with arcgis measurement tool. Is coordinate system in this formula an impact?
Function:
function GetNewPoint(x, y, distance, angle) {
var alpha = ToRadian(angle);
var cos = Math.cos(alpha);
var sin = Math.sin(alpha);
var x2 = (cos * distance)+x ;
var y2 = (sin * distance)+y ;
return esri.geometry.xyToLngLat(x2, y2);
}
function ToRadian(angle) {
return (Math.PI / 180) * angle;
}
The map projection (the thing that is defined by your coordinate system) will absolutely impact the result of the measure.
"Every map projection causes distortion of shapes, areas, directions, and/or distances. Some projections such as Robinson or Winkel Tripel attempt to minimize distortion across the world through some compromise of all those factors. Other projections (such as UTM and State Plane) are designed for focused areas of the globe in order to keep the distortion minimal."
source: https://blogs.esri.com/esri/arcgis/2010/03/05/measuring-distances-and-areas-when-your-map-uses-the-mercator-projection/
Fortunately, the Web API that you are using has some methods to re-project your data before you do any measurement.
If you are interested in re-project a specific feature of your map, you can use something like this:
var sr = new esri.SpatialReference({wkid:32610})
// assuming you already referenced geometryService
geometryService.project([graphic], sr, function(projectedGraphic){
geometryService.areasAndLengths(projectedGraphic, function(result){
var perimeter = result.lengths[0];
var area = result.areas[0];
});
});
The previous example assumes the usage of a geometry service (you can find this at ArcGIS Online or at your ArcGIS for Server Instance) and is remotely processed.
You can find more information about Geometry Service at this link.
Let me know if this information helps to solve your problem.
Check this website he put all the equation you need to work with coordiantes and HERE is my solution to get the coordiantes of the new point.

Match center of a Tile Layer with center of the Map on Leaflet

I am trying to make a Map of a Distribution Center using a flat image of the place. I need to be able to zoom in and out keeping a good quality of the image. So far I've been using Leaflet to draw the Map and MapTiler to create a tile schema that makes possible zooming without any quality loss and good performance.
I have everything set and working, but I need my Tile Layer center to match with the Map center. No matter what I do the Tile Layer top-left corner always starts at the (0,0) coordinate of the Map.
At zoom level 3, for example, my image is 1536x1280px, so the top-left corner should be at coordinate (-768,640) of the Map when talking about absolute pixel coordinates.
Here is my main script:
var map = L.map('map',{
crs: L.CRS.Simple,
center:[0,0]
}).setView([0,0],3);
//mapHeight and mapWidth are the pixel dimensions of the flat image at max zoom level.
//In this case it's 6144x4608px. By default the tiles size is 256x256px.
var southWest = map.unproject([ 0, mapHeight], getMaxZoom());
var northEast = map.unproject([ mapWidth, 0 ], getMaxZoom());
var mapBounds = new L.LatLngBounds(southWest, northEast);
L.tileLayer('tile/{z}/{x}/{y}.png',{
minZoom: 2,
maxZoom: 5,
noWrap:true,
bounds:mapBounds
}).addTo(map);
I have messed with the center, setView and bounds, but no success in making the Tile Layer move.
The documentation of Leaflet can be found here http://leafletjs.com/reference.html
Please help me if you can. Thank you.
In Leaflet, tile coordinates are tightly bound to an internal mechanism - pixel coordinates.
For every zoom level, coordinates in LatLng get projected to the CRS coordinates (that's EPSG:4326→EPSG:3857 for earth maps and a vertical flip for L.CRS.Simple maps), then the CRS coordinate gets scaled by a factor dependant on the zoom level to give a pixel coordinate. Layers (tiles, markers, etc) are drawn using these pixel coordinates.
For "normal" tiles (of both GridLayer and TileLayer), the {x} and {y} fields on the tile template URL are simply the pixel coordinate divided by the tile size. A 256px tile at pixel [0, 0] will have the tile coords [0, 0], a tile at pixel [512, 256] will have [2, 1] and so on.
If you read the code for L.TileLayer.WMS, however, you'll notice that the tile URLs don't necessarily depend on the tile coordinates.
Back to your problem. You can overcome it by using the same strategy of L.TileLayer.WMS: overriding the getTileUrl method, with something like:
var t = L.tileLayer(…);
t.getTileUrl = function(coords) {
var myZ = coords.z;
var myX = coords.x + (8 * Math.pow(2, myZ - 3));
var myY = coords.y + (6 * Math.pow(2, myZ - 3));
return '/tile/' + myZ + '/' + myX + '/' + myY + '.png';
}
The code is simplistic (and I haven't bothered to make the math so that things fit where they should), but that should put you on the right track.
A different way to achieve the same would be to create a custom L.CRS based on L.CRS.Simple which applies a translation transformation to convert between LatLngs and CRS coordinates. If the code in src/geo/crs/CRS.Simple.js and src/geometry/Transformation.js in the Leaflet repo makes sense to you, I suggest you try this approach.

Finding a set of coordinates within a certain range from latitude and longitide

I am working on a project in javascript involving google maps.
The goal is to figure out 16-20 coordinate points within n kilometers from a set of latitude longitude coordinates such that the 16 points if connected will form a circle around the original coordinates.
The end goal is to make it so I can figure out coordinates to plot and connect on google maps to make a circle around a given set of coordinates.
The code would go something like:
var coordinates = Array();
function findCoordinates(lat, long, range) {
}
coordinates = findCoordinates(-20, 40, 3);
Now to make the magic happen in the findCoordinates() function.
Basically what you're trying to do is find N points on the radius of a circle from a given point with a given radius. One simple way of doing it is splitting the 360 degrees of a circle in to N equal chunks, and finding the points at regular intervals.
The following should do roughly what you're after -
function findCoordinates(lat, long, range)
{
// How many points do we want? (should probably be function param..)
var numberOfPoints = 16;
var degreesPerPoint = 360 / numberOfPoints;
// Keep track of the angle from centre to radius
var currentAngle = 0;
// The points on the radius will be lat+x2, long+y2
var x2;
var y2;
// Track the points we generate to return at the end
var points = [];
for(var i=0; i < numberOfPoints; i++)
{
// X2 point will be cosine of angle * radius (range)
x2 = Math.cos(currentAngle) * range;
// Y2 point will be sin * range
y2 = Math.sin(currentAngle) * range;
// Assuming here you're using points for each x,y..
p = new Point(lat+x2, long+y2);
// save to our results array
points.push(p);
// Shift our angle around for the next point
currentAngle += degreesPerPoint;
}
// Return the points we've generated
return points;
}
The array of points you get back can then easily be used to draw the circle you wish on your google map.
If your overall goal however is just to draw a circle at a fixed radius around a point, then a far easier solution may be to use an overlay. I've found KMBox to be very easy to set up - you give it a central point, a radius and an image overlay (in your case, a transparent circle with a visible line around the edge) and it takes care of everything else, including resizing it on zoom in/out.
I had to find some code to calculate Great Circle distances a while back (just Google "Great Circle" if you don't know what I'm talking about) and I found this site:
http://williams.best.vwh.net/gccalc.htm
You might be able to build up your own JavaScript code to do your lat/lon range calculations using the JavaScript from that site as a reference. It sounds to me like you just need to divide up the 360 degrees of a circle into an equal number of pieces and draw a line out to an equal distance from the center at each "bearing". Once you know the lat/lon at the other end of each bearing/distance line, then connecting the dots to form a polygon is trivial.

Categories