I am making hexagon grid for my game based on Google Map v3 and got a problem.
After I click in one hexagon are showing differents values, not one the same as for marker inside of the all hexagon.
The right value is showing just in the left down corner of quarter hexagon.
The value of coord_slug is making based on coordinates lat, lng.
What I have to do hexagon and marker values being the same ?
In this way 55.3,14.8 for upper and 55.25,1485 for down hexagon.
I need those values in a game for downloading dates from database.
The part responsible for displaying the value:
function set_window(event) {
// Set Parameters
var lat = event.latLng.lat();
var lng = event.latLng.lng();
var coord_slug = (Math.round(lat * 20) / 20) + ',' + (Math.round(lng * 20) / 20);
alert(coord_slug);
}
The working part of the script here:
function round_down(n) {
if (n > 0) {
return Math.ceil(n / 0.05) * 0.05;
} else {
return 0;
}
}
var map;
var pointCount = 0;
var locations = [];
var gridWidth = 3660; // hex tile size in meters
var bounds;
var places = [
[55.3, 14.8],
[55.25, 14.85],
]
var SQRT3 = 1.73205080756887729352744634150587236;
$(document).ready(function(){
bounds = new google.maps.LatLngBounds();
map = new google.maps.Map(document.getElementById("map_canvas"), {center: {lat: 55.27, lng: 14.8}, zoom: 10});
// Adding a marker just so we can visualize where the actual data points are.
// In the end, we want to see the hex tile that contain them
places.forEach(function(place, p){
latlng = new google.maps.LatLng({lat: place[0], lng: place[1]});
marker = new google.maps.Marker({
position: latlng,
map: map})
marker.addListener('click', set_window);
// Fitting to bounds so the map is zoomed to the right place
bounds.extend(latlng);
});
// Now, we draw our hexagons! (or try to)
locations = makeBins(places);
locations.forEach(function(place, p){
drawHorizontalHexagon(map, place, gridWidth);
})
});
function drawHorizontalHexagon(map, position, radius){
var coordinates = [];
for(var angle= 0;angle < 360; angle+=60) {
coordinates.push(google.maps.geometry.spherical.computeOffset(position, radius, angle));
}
// Construct the polygon.
var polygon = new google.maps.Polygon({
paths: coordinates,
position: position,
strokeColor: '#FF0000',
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: '#FF0000',
fillOpacity: 0.35,
geodesic: true
});
polygon.setMap(map);
polygon.addListener('click', set_window);
}
// Below is my attempt at porting binner.py to Javascript.
// Source: https://github.com/coryfoo/hexbins/blob/master/hexbin/binner.py
function distance(x1, y1, x2, y2){
console.log(x1, y1, x2, y2);
result = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
console.log("Distance: ", result);
return
}
function nearestCenterPoint(value, scale){
div = value / (scale/2);
console.log("div", div);
mod = value % (scale/2);
console.log("mod", mod);
if(div % 2 == 1){
increment = 1;
} else{
increment = 0;
}
rounded = scale / 2 * (div + increment);
if(div % 2 === 0){
increment = 1;
} else{
increment = 0;
}
rounded_scaled = scale / 2 * (div + increment);
result = [rounded, rounded_scaled]
console.log("nearest centerpoint to", value, result);
return result;
}
function makeBins(data){
bins = [];
data.forEach(function(place, p){
x = place[0];
y = place[1];
console.log("Original location:", x, y);
px_nearest = nearestCenterPoint(x, gridWidth);
py_nearest = nearestCenterPoint(y, gridWidth * SQRT3);
z1 = distance(x, y, px_nearest[0], py_nearest[0]);
z2 = distance(x, y, px_nearest[1], py_nearest[1]);
if(z1 > z2){
bin = new google.maps.LatLng({lat: px_nearest[0], lng: py_nearest[0]});
console.log("Final location:", px_nearest[0], py_nearest[0]);
} else {
bin = new google.maps.LatLng({lat: px_nearest[1], lng: py_nearest[1]});
console.log("Final location:", px_nearest[1], py_nearest[1]);
}
bins.push(bin);
})
return bins;
}
function set_window(event) {
// Set Parameters
var lat = event.latLng.lat();
var lng = event.latLng.lng();
var coord_slug = (Math.round(lat * 20) / 20) + ',' + (Math.round(lng * 20) / 20);
alert(coord_slug);
}
<html>
<head>
<script data-require="jquery#*" data-semver="2.2.0" src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script data-require="bootstrap#*" data-semver="3.3.6" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<link data-require="bootstrap-css#3.3.6" data-semver="3.3.6" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css" />
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?libraries=geometry"></script>
</head>
<body>
<div id="map_canvas" style="width:100%; height:80vh;">
</div>
</body>
</html>
Additional link:
The working part of the script in Plunger
You are setting a position attribute to your Polygons, which seems to be what you want to display...
So you can replace the following
polygon.addListener('click', set_window);
By this:
polygon.addListener('click', function() {
var polyPosition = this.position.lat() + ', ' + this.position.lng();
alert(polyPosition);
});
Related
Note: The problem is not specific to Leaflet, but GIS in general.
I'm trying to draw an arc on a map. I have a function to generate the polygon points and it works on a canvas for example, but not on Lng,Lat map.
The problem is that I cannot figure out how to convert the inner/outer radius from Meters to degrees (as in lng/lat), what I tried so far looks more elliptic than circular.
How to accurately convert meters to longitude or latitude at any point on earth (except the poles)?
Here is what I tried (works) on canvas.
$(document).ready(function() {
var d_canvas = document.getElementById('canvas');
var c2 = d_canvas.getContext('2d');
c2.fillStyle = '#f00';
c2.beginPath();
var fromDeg = 0;
var toDeg = 90;
var fromRad = getAngle(fromDeg);
var toRad = getAngle(toDeg);
var segments = 100;
var step = getAngle(toDeg-fromDeg)/segments;
var x = 250;
var y = 250;
var outR = 250;
var inR = 230;
c2.moveTo(x+(Math.sin(fromRad)*inR),y-(Math.cos(fromRad)*inR));
//c2.moveTo(x,y);
for (var i = fromRad; i<=toRad; i=i+step){
c2.lineTo(x+(Math.sin(i)*inR),y-(Math.cos(i)*inR));
}
//c2.closePath();
for (var i = toRad; i>=fromRad; i=i-step){
c2.lineTo(x+(Math.sin(i)*outR),y-(Math.cos(i)*outR));
}
c2.lineTo(x+(Math.sin(fromRad)*inR),y-(Math.cos(fromRad)*inR));
//c2.closePath();
c2.stroke();
});
function getAngle(deg){
var val = 2*(deg/360);
return Math.PI*val;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas" width="500" height="500"></canvas>
And here is what I tried ( dosn't work well ) on Leaflet map.
var osmUrl = 'http://{s}.tile.osm.org/{z}/{x}/{y}.png',
osmAttrib = '© OpenStreetMap contributors',
osm = L.tileLayer(osmUrl, {
maxZoom: 18,
attribution: osmAttrib
});
// initialize the map on the "map" div with a given center and zoom
var map = L.map('map').setView([59.56667, 150.80000], 12).addLayer(osm);
// Script for adding marker on map click
L.polygon(getPolygon()).addTo(map);
function getPolygon() {
var fromDeg = 0;
var toDeg = 90;
var fromRad = getAngle(fromDeg);
var toRad = getAngle(toDeg);
var segments = 100;
var step = getAngle(toDeg - fromDeg) / segments;
var y = 150.84229;
var x = 59.55416;
var outR = 0.05; // <------ should be dynamic?
var inR = 0.025; // <------ this also?
var polygon = [];
polygon.push([x + (Math.sin(fromRad) * inR), y + (Math.cos(fromRad) * inR)]);
for (var i = fromRad; i <= toRad; i = i + step) {
polygon.push([x + (Math.sin(i) * inR), y + (Math.cos(i) * inR)]);
}
//c2.closePath();
for (var i = toRad; i >= fromRad; i = i - step) {
polygon.push([x + (Math.sin(i) * outR), y + (Math.cos(i) * outR)]);
}
polygon.push([x + (Math.sin(fromRad) * inR), y + (Math.cos(fromRad) * inR)]);
return polygon;
}
function getAngle(deg) {
var val = 2 * (deg / 360);
return Math.PI * val;
}
#map {
height: 500px;
width: 80%;
}
<script src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js"></script>
<link href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css" rel="stylesheet" />
<script src="http://unpkg.com/leaflet-arc/bin/leaflet-arc.min.js"></script>
<div id="map"></div>
So your original question is
How to accurately convert meters to longitude or latitude at any point on earth (except the poles)?
But my brain reads that as
Given a [lat, lng] point and a distance d in meters, how to calculate a second [lat2, lng2] point which is d meters away from the first point?
Which, if you know some GIS jargon, is the same as asking
How do I solve the direct geodesic problem?
The answer involves mathematical concepts such as ellipsoids and great circles.
But given that you're working with Javascript and Leaflet, I'll just jump to practical implementations.
If you need a super-accurate answer, you want to have a look at the JS implementation of GeographicLib, and its methods to solve the direct geodesic problem.
If you don't really care about accuracy (and specially do not care about accuracy at the poles), you want to have a look at cheap-ruler, and specifically its destination(p, dist, bearing) method.
There are more solutions, like using a equidistant map projection centered on the point, or some other implementations of the geodesic problems, or some turf.js trickery, or creating the geometries outside of JS with similar methods, or whatever.
This problem has been solved already, so I advise to use any of the existing solutions.
This solved the problem
var osmUrl = 'http://{s}.tile.osm.org/{z}/{x}/{y}.png',
osmAttrib = '© OpenStreetMap contributors',
osm = L.tileLayer(osmUrl, {
maxZoom: 18,
attribution: osmAttrib
});
// initialize the map on the "map" div with a given center and zoom
var map = L.map('map').setView([59.56667, 150.80000], 12).addLayer(osm);
// Script for adding marker on map click
L.polygon(getPolygon()).addTo(map);
function getPolygon() {
var fromDeg = 0;
var toDeg = 120;
var lat = 59.56667;
var lon = 150.80000;
var outR = 200;
var inR = 180;
var polygon = [];
for (var i = fromDeg; i <= toDeg; i++) {
polygon.push(getPoint(lat, lon, inR, i));
}
for (var i = toDeg; i >= fromDeg; i--) {
polygon.push(getPoint(lat, lon, outR, i));
}
polygon.push(getPoint(lat, lon, inR, fromDeg));
return polygon;
}
/*************************
* The solution
*************************/
function getPoint(lat, lon, r, deg) {
lat2 = (r / 111230) * Math.cos(deg2rad(deg));
lat2 += lat;
lon2 = (r / 111230) * Math.sin(deg2rad(deg));
lon2 = lon2 * (1 / Math.cos(deg2rad(lat2)));
lon2 += lon;
return [lat2, lon2];
}
function deg2rad(deg) {
return deg * (Math.PI / 180);
}
#map {
height: 500px;
width: 80%;
}
<link href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css" rel="stylesheet"/>
<script src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js"></script>
<div id="map"></div>
Can anyone please help me in connecting specified points with an arc or parabola or any curve line in my google map? It is currently connected with a straight line. I have tried using the other stack overflow question where this was asked but those codes only connects two points, I might have multiple markers in the future so i need something that can be resused over and over... Please see the codes below...
<!DOCTYPE html>
<html>
<head>
<script src="http://maps.googleapis.com/maps/api/js"></script>
<script>
var x=new google.maps.LatLng(14.560774500000008,121.02631410000006);
var rcbc=new google.maps.LatLng(14.560648, 121.016781);
var powerplant=new google.maps.LatLng(14.565066, 121.036184);
var trafalgar=new google.maps.LatLng(14.560774500000008,121.02631410000006);
var mapua=new google.maps.LatLng(14.562883, 121.022039);
function initialize()
{
var mapProp = {
center:x,
zoom:15,
mapTypeId:google.maps.MapTypeId.ROADMAP
};
var map=new google.maps.Map(document.getElementById("googleMap"),mapProp);
var myTrip=[trafalgar,rcbc];
var myTrip2=[rcbc,powerplant];
var flightPath2=new google.maps.Polyline({
path:myTrip2,
strokeColor:"##35495e",
strokeOpacity:0.8,
strokeWeight:3
});
var flightPath=new google.maps.Polyline({
path:myTrip,
strokeColor:"##35495e",
strokeOpacity:0.8,
strokeWeight:3
});
var marker=new google.maps.Marker({
position:x,
});
var marker2=new google.maps.Marker({
position:london,
});
var marker3=new google.maps.Marker({
position:rcbc,
});
var marker4=new google.maps.Marker({
position:powerplant,
});
marker4.setMap(map);
marker3.setMap(map);
marker2.setMap(map);
marker.setMap(map);
flightPath.setMap(map);
flightPath2.setMap(map);
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<div id="googleMap" style="width:1000px;height:680px;"></div>
</body>
</html>
One option would be to use the code from this related question: How to display curved polyline on one side of a straight line? (a Bezier curve).
proof of concept fiddle
code snippet:
function drawCurve(P1, P2, map) {
var lineLength = google.maps.geometry.spherical.computeDistanceBetween(P1, P2);
var lineHeading = google.maps.geometry.spherical.computeHeading(P1, P2);
if (lineHeading < 0) {
var lineHeading1 = lineHeading + 45;
var lineHeading2 = lineHeading + 135;
} else {
var lineHeading1 = lineHeading + -45;
var lineHeading2 = lineHeading + -135;
}
var pA = google.maps.geometry.spherical.computeOffset(P1, lineLength / 2.2, lineHeading1);
var pB = google.maps.geometry.spherical.computeOffset(P2, lineLength / 2.2, lineHeading2);
var curvedLine = new GmapsCubicBezier(P1, pA, pB, P2, 0.01, map);
}
var x = new google.maps.LatLng(14.560774500000008, 121.02631410000006);
var london = new google.maps.LatLng(51.5073509, -0.12775829999998223);
var rcbc = new google.maps.LatLng(14.560648, 121.016781);
var powerplant = new google.maps.LatLng(14.565066, 121.036184);
var trafalgar = new google.maps.LatLng(14.560774500000008, 121.02631410000006);
var mapua = new google.maps.LatLng(14.562883, 121.022039);
function initialize() {
var mapProp = {
center: x,
zoom: 15,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("googleMap"), mapProp);
var myTrip = [trafalgar, rcbc];
var myTrip2 = [rcbc, powerplant];
drawCurve(trafalgar, rcbc, map);
drawCurve(rcbc, powerplant, map);
var marker = new google.maps.Marker({
position: x,
});
var marker2 = new google.maps.Marker({
position: london,
});
var marker3 = new google.maps.Marker({
position: rcbc,
});
var marker4 = new google.maps.Marker({
position: powerplant,
});
marker4.setMap(map);
marker3.setMap(map);
marker2.setMap(map);
marker.setMap(map);
flightPath.setMap(map);
flightPath2.setMap(map);
}
google.maps.event.addDomListener(window, 'load', initialize);
// original Belzier Curve code from nicoabie's answer to this question on StackOverflow:
// https://stackoverflow.com/questions/5347984/letting-users-draw-curved-lines-on-a-google-map
var GmapsCubicBezier = function(latlong1, latlong2, latlong3, latlong4, resolution, map) {
var lat1 = latlong1.lat();
var long1 = latlong1.lng();
var lat2 = latlong2.lat();
var long2 = latlong2.lng();
var lat3 = latlong3.lat();
var long3 = latlong3.lng();
var lat4 = latlong4.lat();
var long4 = latlong4.lng();
var points = [];
for (it = 0; it <= 1; it += resolution) {
points.push(this.getBezier({
x: lat1,
y: long1
}, {
x: lat2,
y: long2
}, {
x: lat3,
y: long3
}, {
x: lat4,
y: long4
}, it));
}
var path = [];
for (var i = 0; i < points.length - 1; i++) {
path.push(new google.maps.LatLng(points[i].x, points[i].y));
path.push(new google.maps.LatLng(points[i + 1].x, points[i + 1].y, false));
}
var Line = new google.maps.Polyline({
path: path,
geodesic: true,
strokeColor: "##35495e",
strokeOpacity: 0.8,
strokeWeight: 3
/* dashed line:
icons: [{
icon: {
path: 'M 0,-1 0,1',
strokeOpacity: 1,
scale: 4
},
offset: '0',
repeat: '20px'
}], */
});
Line.setMap(map);
return Line;
};
GmapsCubicBezier.prototype = {
B1: function(t) {
return t * t * t;
},
B2: function(t) {
return 3 * t * t * (1 - t);
},
B3: function(t) {
return 3 * t * (1 - t) * (1 - t);
},
B4: function(t) {
return (1 - t) * (1 - t) * (1 - t);
},
getBezier: function(C1, C2, C3, C4, percent) {
var pos = {};
pos.x = C1.x * this.B1(percent) + C2.x * this.B2(percent) + C3.x * this.B3(percent) + C4.x * this.B4(percent);
pos.y = C1.y * this.B1(percent) + C2.y * this.B2(percent) + C3.y * this.B3(percent) + C4.y * this.B4(percent);
return pos;
}
};
html,
body,
#googleMap {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px
}
<script src="https://maps.googleapis.com/maps/api/js?libraries=geometry"></script>
<div id="googleMap"></div>
When you load the Google Maps JS, specify the geometry library:
<script src="http://maps.googleapis.com/maps/api/js?libraries=geometry"></script>
This means you can then add the geodesic attribute when you create the polylines, e.g.
var flightPath2=new google.maps.Polyline({
path:myTrip2,
geodesic: true,
strokeColor:"##35495e",
strokeOpacity:0.8,
strokeWeight:3
});
"When true, edges of the polygon are interpreted as geodesic and will
follow the curvature of the Earth. When false, edges of the polygon
are rendered as straight lines in screen space."
See Google's examples and documentation:
https://developers.google.com/maps/documentation/javascript/geometry#Navigation
https://developers.google.com/maps/documentation/javascript/examples/geometry-headings
https://developers.google.com/maps/documentation/javascript/3.exp/reference#PolylineOptions
I'm using the following function to generate random geo coordinates within a specified radius from a seed point:
function randomGeo(center, radius) {
var y0 = center.latitude;
var x0 = center.longitude;
var rd = radius / 111300;
var u = Math.random();
var v = Math.random();
var w = rd * Math.sqrt(u);
var t = 2 * Math.PI * v;
var x = w * Math.cos(t);
var y = w * Math.sin(t);
var xp = x / Math.cos(y0);
return {
'latitude': y + y0,
'longitude': xp + x0
};
}
I do this in a loop, several times, using a 2000m radius and the following seed point:
location: { // Oxford
latitude: 51.73213,
longitude: -1.20631
}
I'd expect all of these results to be within 2000m; instead, I'm seeing values upwards of 10000m:
[ { latitude: 51.73256540025445, longitude: -1.3358092771716716 }, // 3838.75070783092
{ latitude: 51.7214165686511, longitude: -1.1644147572878725 }, // 3652.1890457730474
{ latitude: 51.71721400063117, longitude: -1.2082082568884593 }, // 8196.861603477768
{ latitude: 51.73583824510363, longitude: -1.0940424351649711 }, // 5104.820455873758
{ latitude: 51.74017571473442, longitude: -1.3150742602532257 }, // 4112.3279147866215
{ latitude: 51.73496163915278, longitude: -1.0379454413532996 }, // 9920.01459343298
{ latitude: 51.73582333121239, longitude: -1.0939302282840453 }, // 11652.160906253064
{ latitude: 51.72145745285658, longitude: -1.2491630482776055 }, // 7599.550622138115
{ latitude: 51.73036335927129, longitude: -1.3516902043395063 }, // 8348.276271205428
{ latitude: 51.748104753808924, longitude: -1.2669212014250266 }, // 8880.760669882042
{ latitude: 51.72010719621805, longitude: -1.327161328951446 }, // 8182.466715589904
{ latitude: 51.725727610071125, longitude: -1.0691503599266818 } ] // 2026.3687763449955
Given that I (shamelessly!) plagiarized this solution from elsewhere (albeit I've seen several similar implementations), I can't seem to figure out where the math is going wrong.
(Also, in case you want it, this is how I'm calculating the distance. Pretty sure this is correct.)
function distance(lat1, lon1, lat2, lon2) {
var R = 6371000;
var a = 0.5 - Math.cos((lat2 - lat1) * Math.PI / 180) / 2 + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * (1 - Math.cos((lon2 - lon1) * Math.PI / 180)) / 2;
return R * 2 * Math.asin(Math.sqrt(a));
}
The problem seems to stem from the fact that this is just an inaccurate calculation depending on which center point you are using. Particularly this line:
var xp = x / Math.cos(y0);
Removing this line and changing longitude to
'longitude': x + x0
Seems to keep all of the points within the specified radius, although without this line it seems the points will not completely fill out east to west in some cases.
Anyway, I found someone experiencing a similar issue here with someone elses Matlab code as a possible solution. Depends on how uniformly spread out you need the random points if you wanted to work with a different formula.
Here is a google maps visualization of what's going on with your provided formula:
<!doctype html>
<html>
<head>
<script type="text/javascript" src="//maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
var distanceLimit = 2000; //in meters
var numberRandomPoints = 200;
var mapZoomLevel = 11;
var locationindex = 0;
var locations = [
{'name': 'Oxford, England', 'latitude': 51.73213, 'longitude': -1.20631},
{'name': 'Quito, Ecuador', 'latitude': -0.2333, 'longitude': -78.5167},
{'name': 'Ushuaia, Argentina', 'latitude': -54.8000, 'longitude': -68.3000},
{'name': 'McMurdo Station, Antartica', 'latitude': -77.847281, 'longitude': 166.667942},
{'name': 'Norilsk, Siberia', 'latitude': 69.3333, 'longitude': 88.2167},
{'name': 'Greenwich, England', 'latitude': 51.4800, 'longitude': 0.0000},
{'name': 'Suva, Fiji', 'latitude': -18.1416, 'longitude': 178.4419},
{'name': 'Tokyo, Japan', 'latitude': 35.6833, 'longitude': 139.6833},
{'name': 'Mumbai, India', 'latitude': 18.9750, 'longitude': 72.8258},
{'name': 'New York, USA', 'latitude': 40.7127, 'longitude': -74.0059},
{'name': 'Moscow, Russia', 'latitude': 55.7500, 'longitude': 37.6167},
{'name': 'Cape Town, South Africa', 'latitude': -33.9253, 'longitude': 18.4239},
{'name': 'Cairo, Egypt', 'latitude': 30.0500, 'longitude': 31.2333},
{'name': 'Sydney, Australia', 'latitude': -33.8650, 'longitude': 151.2094},
];
</script>
</head>
<body>
<div id="topbar">
<select id="location_switch">
<script>
for (i=0; i<locations.length; i++) {
document.write('<option value="' + i + '">' + locations[i].name + '</option>');
}
</script>
</select>
<img src="http://google.com/mapfiles/ms/micons/ylw-pushpin.png" style="height:15px;"> = Center
<img src="https://maps.gstatic.com/mapfiles/ms2/micons/red.png" style="height:15px;"> = No Longitude Adjustment
<img src="https://maps.gstatic.com/mapfiles/ms2/micons/pink.png" style="height:15px;"> = With Longitude Adjustment (var xp = x / Math.cos(y0);)
</div>
<div id="map_canvas" style="position:absolute; top:30px; left:0px; height:100%; height:calc(100% - 30px); width:100%;overflow:hidden;"></div>
<script>
var markers = [];
var currentcircle;
//Create the default map
var mapcenter = new google.maps.LatLng(locations[locationindex].latitude, locations[locationindex].longitude);
var myOptions = {
zoom: mapZoomLevel,
scaleControl: true,
center: mapcenter
};
var map = new google.maps.Map(document.getElementById('map_canvas'), myOptions);
//Draw default items
var centermarker = addCenterMarker(mapcenter, locations[locationindex].name + '<br>' + locations[locationindex].latitude + ', ' + locations[locationindex].longitude);
var mappoints = generateMapPoints(locations[locationindex], distanceLimit, numberRandomPoints);
drawRadiusCircle(map, centermarker, distanceLimit);
createRandomMapMarkers(map, mappoints);
//Create random lat/long coordinates in a specified radius around a center point
function randomGeo(center, radius) {
var y0 = center.latitude;
var x0 = center.longitude;
var rd = radius / 111300; //about 111300 meters in one degree
var u = Math.random();
var v = Math.random();
var w = rd * Math.sqrt(u);
var t = 2 * Math.PI * v;
var x = w * Math.cos(t);
var y = w * Math.sin(t);
//Adjust the x-coordinate for the shrinking of the east-west distances
var xp = x / Math.cos(y0);
var newlat = y + y0;
var newlon = x + x0;
var newlon2 = xp + x0;
return {
'latitude': newlat.toFixed(5),
'longitude': newlon.toFixed(5),
'longitude2': newlon2.toFixed(5),
'distance': distance(center.latitude, center.longitude, newlat, newlon).toFixed(2),
'distance2': distance(center.latitude, center.longitude, newlat, newlon2).toFixed(2),
};
}
//Calc the distance between 2 coordinates as the crow flies
function distance(lat1, lon1, lat2, lon2) {
var R = 6371000;
var a = 0.5 - Math.cos((lat2 - lat1) * Math.PI / 180) / 2 + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * (1 - Math.cos((lon2 - lon1) * Math.PI / 180)) / 2;
return R * 2 * Math.asin(Math.sqrt(a));
}
//Generate a number of mappoints
function generateMapPoints(centerpoint, distance, amount) {
var mappoints = [];
for (var i=0; i<amount; i++) {
mappoints.push(randomGeo(centerpoint, distance));
}
return mappoints;
}
//Add a unique center marker
function addCenterMarker(centerposition, title) {
var infowindow = new google.maps.InfoWindow({
content: title
});
var newmarker = new google.maps.Marker({
icon: 'http://google.com/mapfiles/ms/micons/ylw-pushpin.png',
position: mapcenter,
map: map,
title: title,
zIndex: 3
});
google.maps.event.addListenerOnce(map, 'tilesloaded', function() {
infowindow.open(map,newmarker);
});
markers.push(newmarker);
return newmarker;
}
//Draw a circle on the map
function drawRadiusCircle (map, marker, distance) {
currentcircle = new google.maps.Circle({
map: map,
radius: distance
});
currentcircle.bindTo('center', marker, 'position');
}
//Create markers for the randomly generated points
function createRandomMapMarkers(map, mappoints) {
for (var i = 0; i < mappoints.length; i++) {
//Map points without the east/west adjustment
var newmappoint = new google.maps.LatLng(mappoints[i].latitude, mappoints[i].longitude);
var marker = new google.maps.Marker({
position:newmappoint,
map: map,
title: mappoints[i].latitude + ', ' + mappoints[i].longitude + ' | ' + mappoints[i].distance + 'm',
zIndex: 2
});
markers.push(marker);
//Map points with the east/west adjustment
var newmappoint = new google.maps.LatLng(mappoints[i].latitude, mappoints[i].longitude2);
var marker = new google.maps.Marker({
icon: 'https://maps.gstatic.com/mapfiles/ms2/micons/pink.png',
position:newmappoint,
map: map,
title: mappoints[i].latitude + ', ' + mappoints[i].longitude2 + ' | ' + mappoints[i].distance2 + 'm',
zIndex: 1
});
markers.push(marker);
}
}
//Destroy all markers
function clearMarkers() {
for (var i = 0; i < markers.length; i++) {
markers[i].setMap(null);
}
markers = [];
}
$('#location_switch').change(function() {
var newlocation = $(this).val();
clearMarkers();
mapcenter = new google.maps.LatLng(locations[newlocation].latitude, locations[newlocation].longitude);
map.panTo(mapcenter);
centermarker = addCenterMarker(mapcenter, locations[newlocation].name + '<br>' + locations[newlocation].latitude + ', ' + locations[newlocation].longitude);
mappoints = generateMapPoints(locations[newlocation], distanceLimit, numberRandomPoints);
//Draw default items
currentcircle.setMap(null);
drawRadiusCircle(map, centermarker, distanceLimit);
createRandomMapMarkers(map, mappoints);
});
</script>
</body>
</html>
You can generate points with a random bearing and distance from the center by moving some distance using vincenty distances (see this stackoverflow answer). In Python, for example, you could use the geopy package.
import random
from geopy import Point
from geopy.distance import geodesic
def generate_point(center: Point, radius: int) -> Point:
radius_in_kilometers = radius * 1e-3
random_distance = random.random() * radius_in_kilometers
random_bearing = random.random() * 360
return geodesic(kilometers=random_distance).destination(center, random_bearing)
radius = 2000
center = Point(51.73213, -1.20631)
points = [generate_point(center, radius) for _ in range(3000)]
Distances are confirmed with:
assert all(geodesic(center, point).meters <= radius for point in points)
Here a simple Vanilla Javascript solution that works like a charm. I want to give credits where it's due and where I found it : https://gist.github.com/fajarlabs/af9e0859fc29b2107bd1797536d2ff2d
/**
* Generates number of random geolocation points given a center and a radius.
* #param {Object} center A JS object with lat and lng attributes.
* #param {number} radius Radius in meters.
* #param {number} count Number of points to generate.
* #return {array} Array of Objects with lat and lng attributes.
*/
function generateRandomPoints(center, radius, count) {
var points = [];
for (var i=0; i<count; i++) {
points.push(generateRandomPoint(center, radius));
}
return points;
}
/**
* Generates number of random geolocation points given a center and a radius.
*
* #param {Object} center A JS object with lat and lng attributes.
* #param {number} radius Radius in meters.
* #return {Object} The generated random points as JS object with lat and lng attributes.
*/
function generateRandomPoint(center, radius) {
var x0 = center.lng;
var y0 = center.lat;
// Convert Radius from meters to degrees.
var rd = radius/111300;
var u = Math.random();
var v = Math.random();
var w = rd * Math.sqrt(u);
var t = 2 * Math.PI * v;
var x = w * Math.cos(t);
var y = w * Math.sin(t);
var xp = x/Math.cos(y0);
// Resulting point.
return {'lat': y+y0, 'lng': xp+x0};
}
// Usage Example.
// Generates 100 points that is in a 1km radius from the given lat and lng point.
var randomGeoPoints = generateRandomPoints({'lat':24.23, 'lng':23.12}, 1000, 100);
console.log(randomGeoPoints);
I'm trying to make a custom map with markers.
I already got a custom map that work's but when i try to add a marker it results in a blank page.
i have no idea what im doing wrong because i did everything i should do, unless I missed something.
I used custom images that are public available
my correct code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1.0,user-scalable=no" />
<meta charset="utf-8" />
<title>Nexoness Nation - Google Maps</title>
<link rel="shortcut icon" href="https://maps.gstatic.com/favicon3.ico"/>
</head>
<body onload="initialize()">
<div id="map-canvas" style="width: 100%; height: 100%"></div>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
var customMapTypeOptions = {
getTileUrl: function(coord, zoom) {
var normalizedCoord = getNormalizedCoord(coord, zoom);
if (!normalizedCoord) {
return null;
}
var bound = Math.pow(2, zoom);
/*Edit this URL to where you upload your tiles...*/
return "http://nexonessnation.bugs3.com/tile_" + zoom + "_" + normalizedCoord.x + "-" + normalizedCoord.y + ".svg";
},
tileSize: new google.maps.Size(256, 256),
isPng: true,
maxZoom: 3,
minZoom: 0,
name: "Nexoness Nation"
};
var customMapType = new google.maps.ImageMapType(customMapTypeOptions);
// Normalizes the coords that tiles repeat across the x axis (horizontally)
// like the standard Google map tiles.
function getNormalizedCoord(coord, zoom) {
var y = coord.y;
var x = coord.x;
// tile range in one direction range is dependent on zoom level
// 0 = 1 tile, 1 = 2 tiles, 2 = 4 tiles, 3 = 8 tiles, etc
var tileRange = 8 << zoom;
// don't repeat across y-axis (vertically)
if (y < 0 || y >= tileRange) {
return null;
}
// repeat across x-axis
if (x < 0 || x >= tileRange) {
x = (x % tileRange + tileRange) % tileRange;
}
return {
x: x,
y: y
};
}
function initialize() {
var myLatlng = new google.maps.LatLng(0, 0);
var myOptions = {
zoom: 1,
center: myLatlng,
mapTypeControlOptions: {
mapTypeIds: ["Nexoness Nation"]
}
}
map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
map.mapTypes.set('Nexoness Nation', customMapType);
map.setMapTypeId('Nexoness Nation');
}
function addMarkers() {
var bounds = map.getBounds();
var southWest = bounds.getSouthWest();
var northEast = bounds.getNorthEast();
var lngSpan = northEast.lng() - southWest.lng();
var latSpan = northEast.lat() - southWest.lat();
for (var i = 0; i < 10; i++) {
var latLng = new google.maps.LatLng(southWest.lat() + latSpan * Math.random(),
southWest.lng() + lngSpan * Math.random());
var marker = new google.maps.Marker({
position: latLng,
map: map_canvas
});
}
}
</script>
</body>
</html>
Does anybody see what i'm doing wrong?
The ID of your div ("map-canvas") in <div id="map-canvas" style="width: 100%; height: 100%"></div> does not match the id you indicate in your script: map = new google.maps.Map(document.getElementById("**map_canvas**"), myOptions);
Also in the jsfiddle you provided, you need to select no wrap - <in body> instead of onLoad in the second dropdown on the left menu because you're calling your initialize() function in the onLoad of the body.
Update: indeed, I forgot about the markers. First the function addMarkers() wasn't called from initialize(). Also let's not forget to send the "map" as a parameter so we can use it in addMarkers.
Finally getBounds is available after the event bounds_changed is fired, we just need to add a listener on it to get the values.
Here is a jsfiddle that works:
http://jsfiddle.net/M2RD6/4/
I'm trying to create a page using an custom map type image map as a background. I needed to restrict the panning on the image, so that it's not possible to pan past the image, but when panning the map left / right (along the x / longitude axis), the map disappears and I'm getting a "maximum stack size exceeded" (Chrome) or "too much recursion" (Firefox) error.
It turns out I'm not the only one having this issue, but in most of the cases, people had trouble with the decimal signs in the coordinates (, instead of .), but that is not my case since I don't use decimals.
I think this might be my case, but not sure how to go about the undefined latitude. (Mostly because I am quite new to JavaScript in general.)
This is all the code I'm using for the map:
function init() {
var map;
var repeatOnAxisX = false;
var blankTile = "img/map/empty.png";
var min_zoom = 4,
max_zoom = 5;
function getNormalizedCoord(coord, zoom) {
if(!repeatOnAxisX) return coord;
var totalTiles = 1 << (zoom - min_zoom),
y = coord.y,
x = coord.x;
var originX = 1 << (zoom - 1),
originY = 1 << (zoom - 1);
if(y < originX || y >= originX + totalTiles || x < originX || x >= originX + totalTiles) {
return null;
}
x -= originX;
y -= originY;
return {
x : x,
y : y
};
}
var customMapType = new google.maps.ImageMapType({
getTileUrl: function(coord, zoom) {
var normalizedCoord = getNormalizedCoord(coord, zoom);
if(normalizedCoord) {
return "img/map/" + zoom + "/" + normalizedCoord.x + "/" + normalizedCoord.y + ".png";
} else {
return blankTile;
}
},
tileSize: new google.maps.Size(256, 256),
maxZoom: max_zoom,
minZoom: min_zoom,
name: "My Image Map"
});
var mapDiv = document.getElementById("map-canvas"),
myCenter = new google.maps.LatLng(-1, -1),
myOptions = {
zoom: 5,
center: myCenter,
streetViewControl: false,
mapTypeControl: false,
mapTypeControlOptions: {
mapTypeIds: ["custom"]
}
};
map = new google.maps.Map(mapDiv, myOptions);
map.mapTypes.set("custom", customMapType);
map.setMapTypeId("custom");
// Setting up the panning restriction
var allowedBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(-67, -74),
new google.maps.LatLng(67, 74)
);
var boundLimits = {
maxLat: allowedBounds.getNorthEast().lat(),
maxLng: allowedBounds.getNorthEast().lng(),
minLat: allowedBounds.getSouthWest().lat(),
minLng: allowedBounds.getSouthWest().lng()
};
var lastValidCenter = map.getCenter();
var newLat, newLng;
google.maps.event.addListener(map, "center_changed", function() {
center = map.getCenter();
if(allowedBounds.contains(center)) {
// still within valid bounds, so save last valid position
lastValidCenter = map.getCenter();
return;
}
newLat = lastValidCenter.lat();
newLng = lastValidCenter.lng();
if(center.lng() > boundLimits.minLng && center.lng() < boundLimits.maxLng) {
newLng = center.lng();
}
if(center.lat() > boundLimits.minLat && center.lat() < boundLimits.maxLat) {
newLat = center.lat;
}
map.panTo(new google.maps.LatLng(newLat, newLng));
});
}
google.maps.event.addDomListener(window, "load", init);
(I'm using this solution to restrict the panning.)
EDIT: Here is a JSFiddle of the problem.
Any ideas / help is really appreciated! Than you!
In your center_changed listener where you check if your newLat and newLng are out of bounds, you assign to newLng the return value (number) of center.lng(), but to newLat you assign the function center.lat:
if(center.lng() > boundLimits.minLng && center.lng() < boundLimits.maxLng) {
newLng = center.lng();
}
if(center.lat() > boundLimits.minLat && center.lat() < boundLimits.maxLat) {
newLat = center.lat;
}
Obviously, you need to call center.lat:
newLat = center.lat();