I want to do static labels (under my line and parallel my line) for polyline in leaflet.
For example:
http://jsfiddle.net/jypp24oq/5/
firstpolyline.bindLabel('Even polylines can have labels.', { noHide: true });
But 'noHide' parametr not work for polylines? and also I want to do label parallel my line.
How I can do it?
You wont be able to just bind a label to a polyline. It will need additional coordinates based on the line you will want to bind it to.
Checkout the example on fiddle that includes a helper function called "bindLabelEx" that handles the polyline labeling for you:
https://jsfiddle.net/jZv7W/158/
$(document).ready(function() {
L.Polyline.include({
bindLabelEx: function (content, options) {
if (!this.label || this.label.options !== options) {
this.label = new L.Label(options, this);
}
var latlngs = this.getLatLngs();
var nPoint = latlngs.length;
var lats = [];
var lngs = [];
for(var i = 0; i < nPoint; i++) {
lats.push(latlngs[i].lat);
lngs.push(latlngs[i].lng);
}
var minLat = Math.min.apply(null, lats);
var maxLat = Math.max.apply(null, lats);
var minLng = Math.min.apply(null, lngs);
var maxLng = Math.max.apply(null, lngs);
var pointM = {
lat: (minLat + maxLat) / 2,
lng: (minLng + maxLng) / 2
};
this.label.setContent(content);
this._showLabelAdded = true;
this._showLabel({
latlng: pointM
});
}
});
L.RotatedMarker = L.Marker.extend({
_setPos: function(pos) {
L.Marker.prototype._setPos.call(this, pos);
if (L.DomUtil.TRANSFORM) {
// use the CSS transform rule if available
this._icon.style[L.DomUtil.TRANSFORM] += ' rotate(' + this.options.angle + 'deg)';
} else if (L.Browser.ie) {
// fallback for IE6, IE7, IE8
var rad = this.options.angle * L.LatLng.DEG_TO_RAD,
costheta = Math.cos(rad),
sintheta = Math.sin(rad);
this._icon.style.filter += ' progid:DXImageTransform.Microsoft.Matrix(sizingMethod=\'auto expand\', M11=' +
costheta + ', M12=' + (-sintheta) + ', M21=' + sintheta + ', M22=' + costheta + ')';
}
}
});
L.rotatedMarker = function(pos, options) {
return new L.RotatedMarker(pos, options);
};
//example user location
var userLocation = new L.LatLng(28.735, 77.524);
var map = L.map('map').setView(userLocation, 10);
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © CloudMade'
}).addTo(map);
var pointA = new L.LatLng(28.635308, 77.22496);
var pointB = new L.LatLng(28.984461, 77.70641);
//var pointC = new L.LatLng(29.03, 77.20);
//var pointD = new L.LatLng(28.52, 77.45);
var pointM = new L.LatLng( (pointA.lat + pointB.lat) / 2, (pointA.lng + pointB.lng) / 2);
var pointList = [pointA, pointB];
//var pointList = [pointA, pointB, pointC, pointD];
var firstpolyline = new L.Polyline(pointList, {
color: 'red',
weight: 3,
opacity: 0.5,
smoothFactor: 1
});
firstpolyline.addTo(map).bindLabelEx('Even polylines can have labels.', { noHide: true, showLabelAdded: true });
var angle = Math.atan( (pointB.lat - pointA.lat) / (pointB.lng - pointA.lng) );
angle *= 180 / Math.PI + 5;
var marker = L.rotatedMarker(pointM, {
icon: L.divIcon({
className: 'label',
html: 'Do you want me to do?',
iconSize: [160, 40]
}),
angle: -angle
//draggable: true
});
marker.addTo(map);
/*
var ll = marker.getLatLng();
marker.options.angle = -45 * (180 / Math.PI);
market.setLatLng(ll);
*/
});
You can create labels attached to iconless markers located on the middle point of your polylines by calculating and setting each middle point position with help of following formula,
Math.round(arrPolylinePoints.length / 2) - 1;
Related
Is possible to draw a curve line between 2 near points in leaflet, for example with these coordinates:
point_1 = (23.634501, -102.552783)
point_2 = (17.987557, -92.929147)
Thanks.
As #Wrokar implied this can be achieved using Leaflet.curve library. You can simply use the code provided by this gist and replace your coordinates with the ones defined in variable latlng1 & latlng2 respectively.
var latlng1 = [LATITUDE, LONGTITUDE],
latlng2 = [LATITUDE, LONGTITUDE];
Here is a working example:
var map = L.map('mapid').setView([51.505, -10], 1);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
var latlngs = [];
var latlng1 = [23.634501, -102.552783],
latlng2 = [17.987557, -92.929147];
var offsetX = latlng2[1] - latlng1[1],
offsetY = latlng2[0] - latlng1[0];
var r = Math.sqrt(Math.pow(offsetX, 2) + Math.pow(offsetY, 2)),
theta = Math.atan2(offsetY, offsetX);
var thetaOffset = (3.14 / 10);
var r2 = (r / 2) / (Math.cos(thetaOffset)),
theta2 = theta + thetaOffset;
var midpointX = (r2 * Math.cos(theta2)) + latlng1[1],
midpointY = (r2 * Math.sin(theta2)) + latlng1[0];
var midpointLatLng = [midpointY, midpointX];
latlngs.push(latlng1, midpointLatLng, latlng2);
var pathOptions = {
color: 'red',
weight: 3
}
var curvedPath = L.curve(
[
'M', latlng1,
'Q', midpointLatLng,
latlng2
], pathOptions).addTo(map);
body {
padding: 0px;
margin: 0px;
}
#mapid {
height: 300px;
}
<link rel="stylesheet" type="text/css" href="https://unpkg.com/leaflet#1.3.3/dist/leaflet.css">
<script src="https://unpkg.com/leaflet#1.3.3/dist/leaflet.js"></script>
<script src="https://elfalem.github.io/Leaflet.curve/src/leaflet.curve.js"></script>
<div id="mapid"></div>
I want to show the distance between a central marker to each another markers in its own window. Is there any way to calculate distance in angular-google-maps?
Below is the code from here.
map.html
<div id="map_canvas" ng-controller="mainCtrl">
<ui-gmap-google-map center="map.center" zoom="map.zoom" draggable="true" options="options" bounds="map.bounds">
<ui-gmap-markers models="randomMarkers" coords="'self'" icon="'icon'">
</ui-gmap-markers>
</ui-gmap-google-map>
</div>
controller.js
angular.module('appMaps', ['uiGmapgoogle-maps'])
.controller('mainCtrl', function($scope) {
$scope.map = {
center: {
latitude: 40.1451,
longitude: -99.6680
},
zoom: 4,
bounds: {}
};
$scope.options = {
scrollwheel: false
};
var createRandomMarker = function(i, bounds, idKey) {
var lat_min = bounds.southwest.latitude,
lat_range = bounds.northeast.latitude - lat_min,
lng_min = bounds.southwest.longitude,
lng_range = bounds.northeast.longitude - lng_min;
if (idKey == null) {
idKey = "id";
}
var latitude = lat_min + (Math.random() * lat_range);
var longitude = lng_min + (Math.random() * lng_range);
var ret = {
latitude: latitude,
longitude: longitude,
title: 'm' + i
};
ret[idKey] = i;
return ret;
};
$scope.randomMarkers = [];
// Get the bounds from the map once it's loaded
$scope.$watch(function() {
return $scope.map.bounds;
}, function(nv, ov) {
// Only need to regenerate once
if (!ov.southwest && nv.southwest) {
var markers = [];
for (var i = 0; i < 2; i++) {
markers.push(createRandomMarker(i, $scope.map.bounds))
}
$scope.randomMarkers = markers;
}
}, true);
});
Why adding additional code when the function is already in google maps? Just include geometry library and:
google.maps.geometry.spherical.computeDistanceBetween(latLng, latLng)
Docs: https://developers.google.com/maps/documentation/javascript/geometry?hl=en
Haversine formula:
a = sin²(Δφ/2) + cos φ1 ⋅ cos φ2 ⋅ sin²(Δλ/2)
c = 2 ⋅ atan2( √a, √(1−a) )
d = R ⋅ c
javascript code:
var R = 6371000; // metres
var φ1 = lat1.toRadians();
var φ2 = lat2.toRadians();
var Δφ = (lat2-lat1).toRadians();
var Δλ = (lon2-lon1).toRadians();
var a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
Math.cos(φ1) * Math.cos(φ2) *
Math.sin(Δλ/2) * Math.sin(Δλ/2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = R * c;
Source from link
I've used this function all my distance measurement and it works well.
Use my highly optimized open-source package to do the calculations for you. Just one simple line and it will do the rest. We got your back.
Use guide: https://www.npmjs.com/package/haversine-calculator
haversineCalculator(start, end,{unit: 'meter'})
A little background. This map draws polygons on the quarter of each minute. They are labelled as such. That seems to work fine. I am adding a second polygon that is editable and draggable to the map which the user is permitted to select quarter minutes by expanding the handles on the gridSelectBox and then click outside of the selected area. I will be written the quarter minute to a database.
That works but with issues.
Sometimes only every other column or row (Long or Lat) is selected though it is clearly in the selected area. I can only assume it is some sort of rounding area. I did notice that what should be a perfect degree X degree vertices 34.00 X -84.00 appears as 34.00000000000001 X -84.00. Could that be the problem, and if so, how do I correct for it?
fiddle
<html>
<head>
<script type ="text/javascript"
src ="https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry&sensor=false"></script>
<title>Find your Qtr minute locator</title>
</head>
<body style="height:100%;margin:0">
<!-- Declare the div, make it take up the full document body -->
<div id="map-canvas" style="width:100%; height: 100%;"></div>
<script type="text/javascript">
var map;
var qtrArray = [];
var Startlatlng = "";
var llOffset =((1/60)/4);
var drawGridSelectBox = false;
var firstRun = true;
var drawGridBox = false;
var gridOverBox = new google.maps.Polygon();
var gridSelectBox = new google.maps.Polygon();
var gridline;
var polylinesquare;
var latPolylines = [];
var latLabels = [];
var latMapLabel;
var lngPolylines = [];
var lngLabels = [];
var lngMapLabel;
var bounds = new google.maps.LatLngBounds();
function initialize() {
map = new google.maps.Map(document.getElementById("map-canvas"), {
center: new google.maps.LatLng(34.0, -84.0),
zoom: 16,
streetViewControl: true,
mapTypeId: google.maps.MapTypeId.ROADMAP,
scaleControl: true
});
google.maps.event.addListener(map, "click", function (event) {
if (!google.maps.geometry.poly.containsLocation(event.latLng, gridSelectBox)) {
map.setZoom(16);
createGridSelectBox(event.latLng);
ClearGrid();
for(var x=0;x<latPolylines.length;++x){
for(var y=0;y<lngPolylines.length-1;++y){
var latLng=new google.maps.LatLng(latPolylines[x].getPath().getAt(0).lat(),
lngPolylines[y].getPath().getAt(0).lng());
if ((google.maps.geometry.poly.containsLocation(latLng, gridSelectBox))&&(!firstRun))
{
drawGridBox = true;
createGridBox(latLng);
}
}
}
firstRun = false;
map.panTo(event.latLng);
drawGridBox = false;
}});
DrawGridOn();
google.maps.event.addListener(map, "idle", function () {
createGridLines(map.getBounds());
});
}
google.maps.event.addDomListener(window, "load", initialize);
function DrawGridOn() {
drawGridSelectBox = true;
}
function DrawGridOff() {
drawGridSelectBox = false;
}
function createGridLines(bounds) {
for (var i = 0; i < latPolylines.length; i++) {
latPolylines[i].setMap(null);
}
latPolylines = [];
for (var j = 0; j < lngPolylines.length; j++) {
lngPolylines[j].setMap(null);
}
lngPolylines = [];
if (map.getZoom() < 14) return;
var north = bounds.getNorthEast().lat();
var east = bounds.getNorthEast().lng();
var south = bounds.getSouthWest().lat();
var west = bounds.getSouthWest().lng();
// define the size of the grid
var topLat = Math.ceil(north / llOffset) * llOffset;
var rightLong = Math.ceil(east / llOffset) * llOffset;
var bottomLat = Math.floor(south / llOffset) * llOffset;
var leftLong = Math.floor(west / llOffset) * llOffset;
for (var latitude = bottomLat; latitude <= topLat; latitude += llOffset) latPolylines.push(new google.maps.Polyline({
path: [
new google.maps.LatLng(latitude, leftLong), new google.maps.LatLng(latitude, rightLong)],
map: map,
geodesic: true,
strokeColor: "#0000FF",
strokeOpacity: 0.5,
strokeWeight: 1
}));
for (var longitude = leftLong; longitude <= rightLong; longitude += llOffset) lngPolylines.push(new google.maps.Polyline({
path: [
new google.maps.LatLng(topLat, longitude), new google.maps.LatLng(bottomLat, longitude)],
map: map,
geodesic: true,
strokeColor: "#0000FF",
strokeOpacity: 0.5,
strokeWeight: 1
}));
if (map.getZoom() < 15) {
for (var i = 0; i < lngLabels.length; i++) {
lngLabels[i].setMap(null);
}
lngLabels = [];
return;
} // set lngLabels to null
for(var x=0;x<latPolylines.length;++x){
for(var y=0;y<lngPolylines.length-1;++y){
var latLng=new google.maps.LatLng(latPolylines[x].getPath().getAt(0).lat(),
lngPolylines[y].getPath().getAt(0).lng());
var qtrLatLng = ddToQM(latLng.lat(), latLng.lng());
lngLabels.push(new google.maps.Marker({
map:map,
position:latLng,
icon:{ url:"https://chart.googleapis.com/chart?"
+"chst=d_bubble_text_small&chld=bb|"
+ latLng
+"|FFFFFF|000000",
anchor:new google.maps.Point(0,42)
}
}));
}
}
} // end createGridLines
function createGridSelectBox(point) {
// Square limits
var smPoint = point;
var babyOffset = (llOffset/2);
var bottomLeftLat = (Math.floor(point.lat() / llOffset) * llOffset)-babyOffset;
var bottomLeftLong = (Math.floor(point.lng() / llOffset) * llOffset) - babyOffset;
var gridLineSquare = [
new google.maps.LatLng(bottomLeftLat, bottomLeftLong), //lwr left
new google.maps.LatLng(bottomLeftLat, bottomLeftLong + llOffset), //lwr right
new google.maps.LatLng(bottomLeftLat + llOffset, bottomLeftLong + llOffset), //upr right
new google.maps.LatLng(bottomLeftLat + llOffset, bottomLeftLong), //upr left
new google.maps.LatLng(bottomLeftLat, bottomLeftLong)]; //lwr left
if (drawGridSelectBox == true) {
gridSelectBox = new google.maps.Polygon({
path: gridLineSquare,
draggable:true,
geodesic:true,
editable :true,
fillColor: "#FF0000",
fillOpacity: 0.35,
strokeColor: "#CC0099",
strokeOpacity: 0.5,
strokeWeight: 2
});
gridSelectBox.setMap(map);
drawGridSelectBox = false;
}
}
function ClearGrid() {
if (qtrArray) {
for (i in qtrArray) {
qtrArray[i].setMap(null);
}
}
qtrArray=[];
}
function createGridBox(point) {
// Square limits
var smPoint = point;
var bottomLeftLat = Math.floor(point.lat() / llOffset) * llOffset;
var bottomLeftLong = Math.floor(point.lng() / llOffset) * llOffset;
var gridLineSquare = [
new google.maps.LatLng(bottomLeftLat, bottomLeftLong), //lwr left
new google.maps.LatLng(bottomLeftLat, bottomLeftLong + llOffset), //lwr right
new google.maps.LatLng(bottomLeftLat + llOffset, bottomLeftLong + llOffset), //upr right
new google.maps.LatLng(bottomLeftLat + llOffset, bottomLeftLong), //upr left
new google.maps.LatLng(bottomLeftLat, bottomLeftLong)]; //lwr left
if (drawGridBox == true) {
gridOverBox = new google.maps.Polygon({
path: gridLineSquare,
draggable:false,
geodesic:true,
editable :false,
fillColor: "#EAED00",
fillOpacity: 0.35,
strokeColor: "#CC0099",
strokeOpacity: 0.5,
strokeWeight: 2
});
gridOverBox.setMap(map);
qtrArray.push(gridOverBox);
}
}
function ddToQM(alat, alng) {
var latResult, lngResult, dmsResult;
alat = parseFloat(alat);
alng = parseFloat(alng);
latResult = (alat >= 0)? "" : "";
latResult += getDms(alat);
lngResult = (alng >= 0)? "" : "";
lngResult += getDms(alng);
dmsResult = latResult + lngResult;
// Return the resultant string.
return dmsResult;
}
function getDms(val) {
// Required variables
var valDeg, valMin, valSec, interimResult;
var qtrMin;
val = Math.abs(val);
// ---- Degrees ----
valDeg = Math.floor(val);
valMin = Math.floor((val - valDeg) * 60);
valSec = Math.round((val - valDeg - valMin / 60) * 3600 * 1000) / 1000;
if (valSec == 60){
valMin +=1;
valSec = 0;
}
if (valMin == 60){
valMin +=1;
valSec = 0;
}
interimResult = valDeg+"";
if (valMin<10){
valMin = "0"+valMin;
}
interimResult += valMin + "";
switch(valSec){
case 0 : qtrMin = "A";
break;
case 15 : qtrMin = "B";
break;
case 30 : qtrMin = "C";
break;
case 45 : qtrMin = "D";
break;
}
interimResult += qtrMin;
return interimResult;
}
</script>
</body>
</html>
After weeks of ripping my script apart, I finally found it. The issue was the LLOffset constant. The grid boxes are to be 1/4th minute X 1/4th minute. Thus the constant of
var llOffset = (1/60)/4);
or 1 degree/60 minutes / 4
I used the fractional computation and let JavaScript decide the format.
JS determined that 1/60)/4 = 0.004166666666666667 or 18 significant digits yet when it computes a LatLng, it uses only 14 significant digits. Normally the difference would not matter but every so often, that .000000000000006667 was enough to push my polygon anchor into the next grid.
Simply changing llOffset to 0.00416666666667 (the same as google maps LatLng, corrected the problem.
This may be useful information when close tolerance is desired.
I am using google maps API for searching houses in different places in a city. All the houses will be having markers and user will draw multiple polygons around the markers based on his interest.
I need to check what all the locations user selected and show only those markers in maps. With my code, I am able to get it done, but struck with some issue
Once user draw first polygon, I am storing that in a variable and then merging it with the coordinates of second polygon and repeating the same for all the polygons. So, all the polygons coordinates are in a single object. Whenever I draw multiple polygons all of them are getting connected with a polyline because I am saving them in one object and sending that to mapOptions.
How to get rid of this issue?? Following is the code, I am using when user draws a polygon. I want all the polygons to be independent to each-other
function drawFreeHand(){
//the polygon
poly = new google.maps.Polyline({map:map,clickable:false});
//move-listener
var move = google.maps.event.addListener(map,'mousemove',function(e){
poly.getPath().push(e.latLng);
});
//mouseup-listener
google.maps.event.addListenerOnce(map,'mouseup',function(e){
google.maps.event.removeListener(move);
var path = poly.getPath();
poly.setMap(null);
var theArrayofLatLng = path.j;
var ArrayforPolygontoUse = GDouglasPeucker(theArrayofLatLng,50);
multi_poly = myFunction1(ArrayforPolygontoUse)
console.log(multi_poly);
var polyOptions = {
map: map,
fillColor: '#0099FF',
fillOpacity: 0.7,
strokeColor: '#AA2143',
strokeWeight: 2,
clickable: false,
zIndex: 1,
path:multi_poly,
editable: false
}
poly = new google.maps.Polygon(polyOptions);
});
}
function myFunction1(myVar) {
if(ArrayforPolygontoUse != undefined) {
str1 = ArrayforPolygontoUse;
}
else {
var str1 = [];
}
return ArrayforPolygontoUse = $.merge(str1,myVar);
}
A polygon can take an array of arrays of google.maps.LatLng objects. That is what you want to do if you want to keep the paths separate.
function drawFreeHand(){
//the polygon
poly = new google.maps.Polyline({map:map,clickable:false});
//move-listener
var move = google.maps.event.addListener(map,'mousemove',function(e){
poly.getPath().push(e.latLng);
});
//mouseup-listener
google.maps.event.addListenerOnce(map,'mouseup',function(e){
google.maps.event.removeListener(move);
var path = poly.getPath();
poly.setMap(null);
var theArrayofLatLng = path.getArray();
var ArrayforPolygontoUse = GDouglasPeucker(theArrayofLatLng,50);
if (poly && poly.getPaths) {
// if already has one or more paths, get the existing paths
multi_poly = poly.getPaths();
multi_poly_path.push(ArrayforPolygontoUse);
} else {
// first path
multi_poly = ArrayforPolygontoUse;
}
var polyOptions = {
map: map,
fillColor: '#0099FF',
fillOpacity: 0.7,
strokeColor: '#AA2143',
strokeWeight: 2,
clickable: false,
zIndex: 1,
paths:multi_poly,
editable: false
}
poly = new google.maps.Polygon(polyOptions);
});
}
proof of concept fiddle
code snippet:
var geocoder;
var map;
var ArrayforPolygontoUse = [];
function initialize() {
map = new google.maps.Map(
document.getElementById("map_canvas"), {
center: new google.maps.LatLng(37.4419, -122.1419),
zoom: 13,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
google.maps.event.addListener(map, 'rightclick', drawFreeHand);
}
google.maps.event.addDomListener(window, "load", initialize);
function drawFreeHand() {
//the polygon
poly = new google.maps.Polyline({
map: map,
clickable: false
});
//move-listener
var move = google.maps.event.addListener(map, 'mousemove', function(e) {
poly.getPath().push(e.latLng);
});
//mouseup-listener
google.maps.event.addListenerOnce(map, 'mouseup', function(e) {
google.maps.event.removeListener(move);
var path = poly.getPath();
poly.setMap(null);
var theArrayofLatLng = path.getArray();
var ArrayforPolygontoUse = GDouglasPeucker(theArrayofLatLng, 50);
if (poly && poly.getPaths) {
multi_poly = poly.getPaths();
multi_poly_path.push(ArrayforPolygontoUse);
} else {
multi_poly = ArrayforPolygontoUse;
}
// multi_poly = myFunction1(ArrayforPolygontoUse)
// console.log(multi_poly);
var polyOptions = {
map: map,
fillColor: '#0099FF',
fillOpacity: 0.7,
strokeColor: '#AA2143',
strokeWeight: 2,
clickable: false,
zIndex: 1,
paths: multi_poly,
editable: false
}
poly = new google.maps.Polygon(polyOptions);
});
}
// from http://stackoverflow.com/questions/16121236/smoothing-gps-tracked-route-coordinates
/* Stack-based Douglas Peucker line simplification routine
returned is a reduced google.maps.LatLng array
After code by Dr. Gary J. Robinson,
Environmental Systems Science Centre,
University of Reading, Reading, UK
*/
function GDouglasPeucker(source, kink)
/* source[] Input coordinates in google.maps.LatLngs */
/* kink in metres, kinks above this depth kept */
/* kink depth is the height of the triangle abc where a-b and b-c are two consecutive line segments */
{
var n_source, n_stack, n_dest, start, end, i, sig;
var dev_sqr, max_dev_sqr, band_sqr;
var x12, y12, d12, x13, y13, d13, x23, y23, d23;
var F = ((Math.PI / 180.0) * 0.5);
var index = new Array(); /* aray of indexes of source points to include in the reduced line */
var sig_start = new Array(); /* indices of start & end of working section */
var sig_end = new Array();
/* check for simple cases */
if (source.length < 3) return (source); /* one or two points */
/* more complex case. initialize stack */
n_source = source.length;
band_sqr = kink * 360.0 / (2.0 * Math.PI * 6378137.0); /* Now in degrees */
band_sqr *= band_sqr;
n_dest = 0;
sig_start[0] = 0;
sig_end[0] = n_source - 1;
n_stack = 1;
/* while the stack is not empty ... */
while (n_stack > 0) {
/* ... pop the top-most entries off the stacks */
start = sig_start[n_stack - 1];
end = sig_end[n_stack - 1];
n_stack--;
if ((end - start) > 1) { /* any intermediate points ? */
/* ... yes, so find most deviant intermediate point to
either side of line joining start & end points */
x12 = (source[end].lng() - source[start].lng());
y12 = (source[end].lat() - source[start].lat());
if (Math.abs(x12) > 180.0) x12 = 360.0 - Math.abs(x12);
x12 *= Math.cos(F * (source[end].lat() + source[start].lat())); /* use avg lat to reduce lng */
d12 = (x12 * x12) + (y12 * y12);
for (i = start + 1, sig = start, max_dev_sqr = -1.0; i < end; i++) {
x13 = (source[i].lng() - source[start].lng());
y13 = (source[i].lat() - source[start].lat());
if (Math.abs(x13) > 180.0) x13 = 360.0 - Math.abs(x13);
x13 *= Math.cos(F * (source[i].lat() + source[start].lat()));
d13 = (x13 * x13) + (y13 * y13);
x23 = (source[i].lng() - source[end].lng());
y23 = (source[i].lat() - source[end].lat());
if (Math.abs(x23) > 180.0) x23 = 360.0 - Math.abs(x23);
x23 *= Math.cos(F * (source[i].lat() + source[end].lat()));
d23 = (x23 * x23) + (y23 * y23);
if (d13 >= (d12 + d23)) dev_sqr = d23;
else if (d23 >= (d12 + d13)) dev_sqr = d13;
else dev_sqr = (x13 * y12 - y13 * x12) * (x13 * y12 - y13 * x12) / d12; // solve triangle
if (dev_sqr > max_dev_sqr) {
sig = i;
max_dev_sqr = dev_sqr;
}
}
if (max_dev_sqr < band_sqr) { /* is there a sig. intermediate point ? */
/* ... no, so transfer current start point */
index[n_dest] = start;
n_dest++;
} else {
/* ... yes, so push two sub-sections on stack for further processing */
n_stack++;
sig_start[n_stack - 1] = sig;
sig_end[n_stack - 1] = end;
n_stack++;
sig_start[n_stack - 1] = start;
sig_end[n_stack - 1] = sig;
}
} else {
/* ... no intermediate points, so transfer current start point */
index[n_dest] = start;
n_dest++;
}
}
/* transfer last point */
index[n_dest] = n_source - 1;
n_dest++;
/* make return array */
var r = new Array();
for (var i = 0; i < n_dest; i++)
r.push(source[index[i]]);
return r;
}
html,
body,
#map_canvas {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px
}
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk"></script>
<div id="map_canvas" style="border: 2px solid #3872ac;"></div>
I'm trying to put together a Image map with a custom projection. I borrowed code from the following 3 URL's:
https://developers.google.com/maps/documentation/javascript/examples/map-coordinates
https://developers.google.com/maps/documentation/javascript/examples/maptype-image
https://developers.google.com/maps/documentation/javascript/examples/map-projection-simple
I added code to loop through all Lat and Lng values in 10 degree increments, but only some markers actually show.
If I remove the line:
mapType.projection = new MercatorProjection();
then the markers all show as expected. That means I can't use my custom projection, unless there is a better way.
All the Javascript code is below and should show you the moon with markers.
Thanks.
<!DOCTYPE html>
<html>
<head>
<title>Showing pixel and tile coordinates</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<style>
html, body, #map-canvas {
height: 100%;
margin: 0px;
padding: 0px
}
</style>
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp"></script>
<script>
var map;
var TILE_SIZE = 256;
var chicago = new google.maps.LatLng(41.850033,-87.6500523);
function modulo(n, d) {
var result = (n % d + d) % d;
return result;
}
function bound(value, opt_min, opt_max) {
if (opt_min != null) value = Math.max(value, opt_min);
if (opt_max != null) value = Math.min(value, opt_max);
return value;
}
function degreesToRadians(deg) {
return deg * (Math.PI / 180);
}
function radiansToDegrees(rad) {
return rad / (Math.PI / 180);
}
/** #constructor */
function MercatorProjection() {
this.pixelOrigin_ = new google.maps.Point(TILE_SIZE / 2, TILE_SIZE / 2);
this.pixelsPerLonDegree_ = TILE_SIZE / 360;
this.pixelsPerLonRadian_ = TILE_SIZE / (2 * Math.PI);
}
MercatorProjection.prototype.fromLatLngToPoint = function(latLng, opt_point) {
var me = this;
var point = opt_point || new google.maps.Point(0, 0);
var origin = me.pixelOrigin_;
point.x = origin.x + latLng.lng() * me.pixelsPerLonDegree_;
// Truncating to 0.9999 effectively limits latitude to 89.189. This is
// about a third of a tile past the edge of the world tile.
var siny = bound(Math.sin(degreesToRadians(latLng.lat())), -0.9999, 0.9999);
point.y = origin.y + 0.5 * Math.log((1 + siny) / (1 - siny)) * -me.pixelsPerLonRadian_;
return point;
};
MercatorProjection.prototype.fromPointToLatLng = function(point) {
var me = this;
var origin = me.pixelOrigin_;
var lng = (point.x - origin.x) / me.pixelsPerLonDegree_;
var latRadians = (point.y - origin.y) / -me.pixelsPerLonRadian_;
var lat = radiansToDegrees(2 * Math.atan(Math.exp(latRadians)) - Math.PI / 2);
return new google.maps.LatLng(lat, lng);
};
// 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 numTiles = 1 << zoom;
// don't repeat across y-axis (vertically)
if (y < 0 || y >= numTiles) {
return null;
}
// repeat across x-axis
if (x < 0 || x >= numTiles) {
x = modulo(x, numTiles);
}
return {
x: x,
y: y
};
}
function createInfoWindowContent() {
var numTiles = 1 << map.getZoom();
var projection = new MercatorProjection();
var worldCoordinate = projection.fromLatLngToPoint(chicago);
var pixelCoordinate = new google.maps.Point(
worldCoordinate.x * numTiles,
worldCoordinate.y * numTiles);
var tileCoordinate = new google.maps.Point(
Math.floor(pixelCoordinate.x / TILE_SIZE),
Math.floor(pixelCoordinate.y / TILE_SIZE));
return [
'Chicago, IL',
'LatLng: ' + chicago.lat() + ' , ' + chicago.lng(),
'World Coordinate: ' + worldCoordinate.x + ' , ' +
worldCoordinate.y,
'Pixel Coordinate: ' + Math.floor(pixelCoordinate.x) + ' , ' +
Math.floor(pixelCoordinate.y),
'Tile Coordinate: ' + tileCoordinate.x + ' , ' +
tileCoordinate.y + ' at Zoom Level: ' + map.getZoom()
].join('<br>');
}
function initialize() {
var mapType = new google.maps.ImageMapType({
getTileUrl: function(coord, zoom) {
var normalizedCoord = getNormalizedCoord(coord, zoom);
if (!normalizedCoord) {
return null;
}
var bound = Math.pow(2, zoom);
return 'http://mw1.google.com/mw-planetary/lunar/lunarmaps_v1/clem_bw' +
'/' + zoom + '/' + normalizedCoord.x + '/' +
(bound - normalizedCoord.y - 1) + '.jpg';
},
tileSize: new google.maps.Size(TILE_SIZE, TILE_SIZE),
maxZoom: 9,
minZoom: 0,
radius: 1738000,
name: 'MyMap'
});
mapType.projection = new MercatorProjection(); // Remove and Markers Will Show Correctly
var mapOptions = {
zoom: 3,
center: chicago
};
map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
map.mapTypes.set('mymap', mapType);
map.setMapTypeId('mymap');
var coordInfoWindow = new google.maps.InfoWindow();
coordInfoWindow.setContent(createInfoWindowContent());
coordInfoWindow.setPosition(chicago);
coordInfoWindow.open(map);
google.maps.event.addListener(map, 'zoom_changed', function() {
coordInfoWindow.setContent(createInfoWindowContent());
coordInfoWindow.open(map);
});
for (var lat=-80; lat<85; lat+=10) {
for (var lng=-180; lng<180; lng+=10) {
var markerlatlng = new google.maps.LatLng(lat, lng);
var marker = new google.maps.Marker({
position: markerlatlng,
map: map,
title: "Moon Marker"
});
}
}
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<div id="map-canvas"></div>
</body>
</html>
There is nothing wrong with the projection, there seems to be a bug with the rendering of the markers, the missing markers are covered by tiles.
Set the optimized-option of the markers to false and all markers will appear.