I am using google maps to allow the user to draw a polygon then using maps APIs and some sort of geometries draw the min bounded square that cover the drawn polygon at angle of 16 degree i.e. the bounded square should bound the whole polygon area AND should be rotated 16 degree with respect to y-axis.
your help is highly appreciated
Regards
This is a complicated problem. I can outline the steps to get you most of the way there:
Solution Outline
Get the map projection (map.getProjection()) and convert the user polygon to the point plane using projection.fromLatLngToPoint.
Rotate the user polygon -16 degrees.
Calculate your bounding square/polygon for the newly rotated user one.
Rotate your polygon by +16 degrees.
Convert your polygon vertices back to LatLng coordinates using projection.fromPointToLatLng
Resources:
Google Maps Api Projection: https://developers.google.com/maps/documentation/javascript/reference#Projection
Google Maps API Point Plane Documentation: https://developers.google.com/maps/documentation/javascript/maptypes#WorldCoordinates
Polygon transformation algorithims: http://www.kralidis.ca/gis/compcart/polytrans/
The following example demonstrates how to rotate a polygon (google.maps.Polygon class).
Instruction:
draw a polygon
click on the drawn polygon to rotate it
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 13,
center: { lat: 33.678, lng: -116.243 },
mapTypeId: google.maps.MapTypeId.TERRAIN
});
var drawingManager = new google.maps.drawing.DrawingManager({
drawingMode: google.maps.drawing.OverlayType.POLYGON,
drawingControl: true,
drawingControlOptions: {
position: google.maps.ControlPosition.TOP_CENTER,
drawingModes: [
google.maps.drawing.OverlayType.POLYGON
]
}
});
drawingManager.setMap(map);
google.maps.event.addListener(drawingManager, "overlaycomplete", function (event) {
var polygon = event.overlay;
google.maps.event.addListener(polygon, 'click', function (e) {
autoRotatePolygon(polygon, 5);
});
});
}
function autoRotatePolygon(polygon, angle) {
window.setInterval(function () {
rotatePolygon(polygon, 5);
}, 250);
}
function rotatePolygon(polygon,angle) {
var map = polygon.getMap();
var prj = map.getProjection();
var origin = prj.fromLatLngToPoint(polygon.getPath().getAt(0)); //rotate around first point
var coords = polygon.getPath().getArray().map(function(latLng){
var point = prj.fromLatLngToPoint(latLng);
var rotatedLatLng = prj.fromPointToLatLng(rotatePoint(point,origin,angle));
return {lat: rotatedLatLng.lat(), lng: rotatedLatLng.lng()};
});
polygon.setPath(coords);
}
function rotatePoint(point, origin, angle) {
var angleRad = angle * Math.PI / 180.0;
return {
x: Math.cos(angleRad) * (point.x - origin.x) - Math.sin(angleRad) * (point.y - origin.y) + origin.x,
y: Math.sin(angleRad) * (point.x - origin.x) + Math.cos(angleRad) * (point.y - origin.y) + origin.y
};
}
html, body {
height: 100%;
margin: 0;
padding: 0;
}
#map {
height: 100%;
}
<div id="map"></div>
<script async defer src="https://maps.googleapis.com/maps/api/js?callback=initMap&libraries=drawing"></script>
JSFiddle
Related
I'm trying to animate a icon over a polyline like in this example: https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/examples/overlay-symbol-animate.
It works but I want to add my own PNG as the icon that is being animated. This is what I tried:
var line = new google.maps.Polyline({
path: [{ lat: 25.774266, lng: -80.193659 }, { lat: 25.83333, lng: 25.83333 - 77.8999964 }],
icons: [{
icon: CruiseShip //This is my .PNG file
}],
strokeColor: '#ffffff',
strokeWeight: 1,
map: map
});
function animateCircle(line) {
var count = 0;
window.setInterval(function () {
count = (count + 1) % 200;
var icons = line.get('icons');
icons[0].offset = (count / 2) + '%';
line.set('icons', icons);
}, 20);
}
Using the lineSymbol object like in the example does work. How can I add a .PNG file to the polyline instead of the lineSymbol? Couldn't find any documentation on this.
The way you are trying to animate the icon only works for SVG Symbols.
Mike Williams wrote an extension to the Google Maps Javascript API v2 (now deprecated and turned off) called epoly which contains the method .GetPointAtDistance, which can be used to update the position of a "normal" icon along a polyline.
There is a interpolate method in the google.maps.geometry.spherical library which interpolates between two points, but isn't very accurate for large scale features.
proof of concept fiddle
code snippet:
// This example creates a 2-pixel-wide red polyline showing the path of
// the first trans-Pacific flight between Oakland, CA, and Brisbane,
// Australia which was made by Charles Kingsford Smith.
function initMap() {
updatePolylinePrototype();
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 3,
center: {
lat: 0,
lng: -180
},
mapTypeId: 'terrain'
});
var line = new google.maps.Polyline({
path: [{
lat: 25.774266,
lng: -80.193659
}, {
lat: 25.83333,
lng: 25.83333 - 77.8999964
}, {
lat: 28.411413,
lng: -16.5449611
}],
strokeColor: '#ffffff',
strokeWeight: 1,
map: map
});
var marker = new google.maps.Marker({
icon: {
url: "http://earth.google.com/images/kml-icons/track-directional/track-12.png",
anchor: new google.maps.Point(12, 12),
scaledSize: new google.maps.Size(24, 24),
},
position: line.getPath().getAt(0),
map: map
})
var bounds = new google.maps.LatLngBounds();
for (var i = 0; i < line.getPath().getLength(); i++) {
bounds.extend(line.getPath().getAt(i));
}
map.fitBounds(bounds);
animateShip(line, marker);
}
function animateShip(line, marker) {
var count = 0;
var lineDistance = 0;
for (var i = 1; i < line.getPath().getLength(); i++) {
lineDistance += google.maps.geometry.spherical.computeDistanceBetween(line.getPath().getAt(i - 1), line.getPath().getAt(i))
}
window.setInterval(function() {
count = (count + 1) % 200;
marker.setPosition(line.GetPointAtDistance(lineDistance - (lineDistance * count / 200)));
}, 20);
}
function updatePolylinePrototype() {
// === A method which returns a GLatLng of a point a given distance along the path ===
// === Returns null if the path is shorter than the specified distance ===
google.maps.Polyline.prototype.GetPointAtDistance = function(metres) {
// some awkward special cases
if (metres == 0) return this.getPath().getAt(0);
if (metres < 0) return null;
if (this.getPath().getLength() < 2) return null;
var dist = 0;
var olddist = 0;
for (var i = 1;
(i < this.getPath().getLength() && dist < metres); i++) {
olddist = dist;
dist += google.maps.geometry.spherical.computeDistanceBetween(this.getPath().getAt(i), this.getPath().getAt(i - 1));
}
if (dist < metres) {
return null;
}
var p1 = this.getPath().getAt(i - 2);
var p2 = this.getPath().getAt(i - 1);
var m = (metres - olddist) / (dist - olddist);
return new google.maps.LatLng(p1.lat() + (p2.lat() - p1.lat()) * m, p1.lng() + (p2.lng() - p1.lng()) * m);
}
}
/* Always set the map height explicitly to define the size of the div
* element that contains the map. */
#map {
height: 100%;
}
/* Optional: Makes the sample page fill the window. */
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
<div id="map"></div>
<!-- Replace the value of the key parameter with your own API key. -->
<script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=initMap&libraries=geometry">
</script>
I am current using google maps API to plot points on a map. I am looking for the best way to achieve a 'lozenge' type effect between points (see image).
Essentially the thickness of the lozenge will be determined dynamically.
The only way I can see to do this, is to draw 2 circles at the ends of my points, and create a rectangle in between them, with no borders. I don't like this idea, as it essentially adds 3 polygons to my map for each line I have, and means that I would have to then add 3 click events for each line (one on each polygon) to display data that I want to show, when clicking on the line.
Is there any better way to achieve this?
Just create 2 Polylines...
function initialize() {
var map = new google.maps.Map(document.getElementById('map-canvas'), {
zoom: 3,
center: {lat: 20, lng: 0},
mapTypeId: 'terrain'
});
var flightPlanCoordinates = [
{lat: 20, lng: -20},
{lat: 20, lng: 20}
];
new google.maps.Polyline({
path: flightPlanCoordinates,
geodesic: true,
strokeColor: '#FF0000',
strokeOpacity: .3,
strokeWeight: 20,
map: map
});
new google.maps.Polyline({
path: flightPlanCoordinates,
geodesic: true,
strokeColor: '#000000',
strokeOpacity: 1.0,
strokeWeight: 1,
map: map
});
}
initialize();
#map-canvas {
height: 200px;
}
<div id="map-canvas"></div>
<script src="https://maps.googleapis.com/maps/api/js"></script>
Edit:
You mentioned in your comment you need the stroke to be fixed at a given distance from the center line.
You could still use that technique I think, as you can calculate the size in meters of a map pixel, based on the latitude and the zoom level, and as the size of the stroke is also in pixels, you should be able to recalculate the stroke each time you zoom-in/out. It won't be 100% precise since the stroke width must be an integer, but it should work.
It should work if the various Polyline points are more or less on the same latitude, because the resolution of a map with the Mercator projection is dependent on the latitude. So, for example if you have a big Polyline crossing from -70 to +70 latitude, it won't be accurate all the way.
This is the formula you can use to compute the size of 1 map pixel. You would need to replace map.getCenter().lat() by your Polyline center point.
// Calculate the width of 1 map pixel in meters
let scale = 156543.03392 * Math.cos(map.getCenter().lat() * Math.PI / 180) / Math.pow(2, map.getZoom());
Unfortunately, there is an issue with this approach. Although it's undocumented, a Polyline stroke width can't be bigger than 32px.
var map;
var polyBounds;
var polyStroke;
var polyWidth = 50; // Width in meters
function initialize() {
map = new google.maps.Map(document.getElementById('map-canvas'), {
zoom: 12,
center: {
lat: 20,
lng: 0
},
mapTypeId: 'terrain'
});
var flightPlanCoordinates = [{
lat: 20,
lng: -0.5
},
{
lat: 20,
lng: 0.5
}
];
polyStroke = new google.maps.Polyline({
path: flightPlanCoordinates,
geodesic: true,
strokeColor: '#FF0000',
strokeOpacity: .3,
strokeWeight: 0,
map: map
});
new google.maps.Polyline({
path: flightPlanCoordinates,
geodesic: true,
strokeColor: '#000000',
strokeOpacity: 1.0,
strokeWeight: 1,
map: map
});
polyBounds = new google.maps.LatLngBounds();
for (var i = 0; i < flightPlanCoordinates.length; i++) {
polyBounds.extend(flightPlanCoordinates[i]);
}
google.maps.event.addListenerOnce(map, 'idle', setStroke);
google.maps.event.addListener(map, 'zoom_changed', setStroke);
}
function setStroke() {
// Calculate the width of 1 map pixel in meters
let scale = 156543.03392 * Math.cos(polyBounds.getCenter().lat() * Math.PI / 180) / Math.pow(2, map.getZoom());
polyStroke.setOptions({
strokeWeight: Math.ceil(polyWidth / scale)
});
}
initialize();
#map-canvas {
height: 200px;
}
<div id="map-canvas"></div>
<script src="https://maps.googleapis.com/maps/api/js"></script>
So unless you find out that these values allow for your use case, this won't be a viable solution...
The solution you were pointed to in the comments (How to draw a polygon around a polyline in JavaScript?) is still probably your best option.
I actually discovered a much easier way to achieve this using the google maps API.
There are built in functions for getting a bearing of a point based on a start point...
So I created a function that takes the start and end points of a line, and then creates points around these based on the heading - then create a polygon using those points.
var pointCount = 12;
function setLozengePath(startPoint, endPoint) {
var sp = google.maps.geometry.spherical;
var heading = sp.computeHeading(startPoint, endPoint);
var points = [];
for (var i = 0; i <= pointCount; ++i) {
points.push(sp.computeOffset(endPoint, radius, heading + 90 - i * 15));
}
for (var i = 0; i <= pointCount; ++i) {
points.push(sp.computeOffset(startPoint, radius, heading - 90 - i * 15));
}
return points;
}
How do I get the svg image is static when I zoom in on the map, which always remains in the same place.
This is my fiddle
If I use png images works , but it is not visually well for me and is not what i'm looking for.
Help is appreciated
Sorry for my english.
new Fiddle
The anchor is expected to be a Point, not a LatLng.
The default-acnchor is the bottom-middle of the icon, as it seems you need to set it to the top-left, so it has to be:
new google.maps.Point(0,0)
When you want to have a scaled icon based on the zoom you must calculate the scale-property and re-assign the icon to the marker.
The formula would be(assuming the scale-factor at zoom 12 is 1):
Math.pow(2,map.getZoom()-12)
function initialize() {
var mapOptions = {
center: new google.maps.LatLng(-32.95041520, -60.66641804),
zoom: 12,
mapTypeId: google.maps.MapTypeId.TERRAIN
};
map = new google.maps.Map(document.getElementById('map-canvas'),
mapOptions);
triangleCoords = [
new google.maps.LatLng(-32.93831432, -60.69379806),
new google.maps.LatLng(-32.96337859, -60.67860603),
new google.maps.LatLng(-32.96352262, -60.66633224),
new google.maps.LatLng(-32.95041520, -60.66641807)
];
var bermudaTriangle = new google.maps.Polygon({
paths: triangleCoords,
IsInactivo: true
});
bermudaTriangle.setMap(map);
var bounds = new google.maps.LatLngBounds();
var i;
for (i = 0; i < triangleCoords.length; i++) {
bounds.extend(triangleCoords[i]);
}
console.log(bounds.getCenter());
centroPolygon = bounds.getCenter();
var inactive = new google.maps.MVCObject();
inactive.set('icon', {
path: 'M27.314 4.686c-3.022-3.022-7.040-4.686-11.314-4.686s-8.292 1.664-11.314 4.686c-3.022 3.022-4.686 7.040-4.686 11.314s1.664 8.292 4.686 11.314c3.022 3.022 7.040 4.686 11.314 4.686s8.292-1.664 11.314-4.686c3.022-3.022 4.686-7.040 4.686-11.314s-1.664-8.292-4.686-11.314zM28 16c0 2.588-0.824 4.987-2.222 6.949l-16.727-16.727c1.962-1.399 4.361-2.222 6.949-2.222 6.617 0 12 5.383 12 12zM4 16c0-2.588 0.824-4.987 2.222-6.949l16.727 16.727c-1.962 1.399-4.361 2.222-6.949 2.222-6.617 0-12-5.383-12-12z',
fillColor: '#FF5858',
fillOpacity: 0.4,
scale: 1,
strokeColor: '#FF5858',
strokeWeight: 1,
//set the anchor to the top left corner of the svg
anchor: new google.maps.Point(0, 0)
});
google.maps.event.addListener(map, 'zoom_changed', function() {
inactive.get('icon').scale = Math.pow(2, this.getZoom() - 12);
//tell the marker that the icon has changed
inactive.notify('icon');
});
google.maps.event.trigger(map, 'zoom_changed');
new google.maps.Marker({
map: map,
position: centroPolygon
}).bindTo('icon', inactive, 'icon');
}
google.maps.event.addDomListener(window, 'load', initialize)
html,
body,
#map-canvas {
height: 100%;
margin: 0px;
padding: 0px
}
<script src="https://maps.googleapis.com/maps/api/js?v=3&.js"></script>
<div id="map-canvas"></div>
I have a map thats populated with markers of places from a fusion table. I'm taking the users location and displaying a circle of radius 10 miles from their location. Here is my code - http://connormccarra.com/sandbox/map/. How can I use the api to count the number of markers bound by the circle and output that number in the footer?
Cheers!
Relevant code:
var map;
function Initialize() {
var MapOptions = {
zoom: 7,
center: new google.maps.LatLng(53.4125694, -8.245014),
mapTypeId: google.maps.MapTypeId.ROADMAP,
sensor: true
};
map = new google.maps.Map(document.getElementById("map_canvas"), MapOptions);
var layer = new google.maps.FusionTablesLayer({
query: {
select: 'Address',
from: '1OPU6utSjRYwJSFK-EXdaGmt2KgLTq2loVIjS3AA'
}
});
layer.setMap(map);
// Try HTML5 geolocation
if(navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
var pos = new google.maps.LatLng(position.coords.latitude,
position.coords.longitude);
var marker = new google.maps.Marker({
map: map,
position: pos,
content: 'You are here!'
});
// Add circle overlay and bind to marker
var circle = new google.maps.Circle({
map: map,
radius: 16093, // 10 miles in metres
fillColor: '#AA0000'
});
circle.bindTo('center', marker, 'position');
map.setCenter(pos);
}, function() {
handleNoGeolocation(true);
});
} else {
// Browser doesn't support Geolocation
handleNoGeolocation(false);
}
}
function handleNoGeolocation(errorFlag) {
if (errorFlag) {
var content = 'Error: The Geolocation service failed.';
} else {
var content = 'Error: Your browser doesn\'t support geolocation.';
}
var options = {
map: map,
position: new google.maps.LatLng(60, 105),
content: content
};
var count = mgr.getMarkerCount(circle);
document.getElementById("Address").innerHTML += count + "<BR>";
google.maps.event.addDomListener(window, 'load', initialize);
//Maps API loaded, now load customizations
var element = document.createElement('script');
element.src = 'template.js';
element.type = 'text/javascript';
var scripts = document.getElementsByTagName('script')[0];
scripts.parentNode.insertBefore(element, scripts);
}
The markers created by a FusionTableLayer are not real markers, there is no way to get them as a kind of list to filter them(you can't get any details for the markers, except you click them).
But you may request the FusionTableAPI with a spatial condition(via AJAX, jsonp is supported).
The syntax for the query:
SELECT COUNT() from tableId
WHERE ST_INTERSECTS('Address',CIRCLE(LATLNG(lat,lng),10000))
How to send a query : https://developers.google.com/fusiontables/docs/v1/sql-reference
Demo(using data of another FusionTable because your table is protected):
http://jsfiddle.net/doktormolle/bAtgf/
Simplest way: use the geometry library computeDistanceBetween method. If the distance from the user's location is less than 10 miles, the marker is in the circle.
I suggest you first fetch all the coordinates of your FusionTablesLayer.
Here is an example which was used in the sidebar
http://www.geocodezip.com/v3_FusionTables_AfricaMap_kml_sidebar.html
Then using a loop statement you can use the computeDistanceBetween function.
Detect If Marker is Within Circle Overlay
I am trying to move my marker (icon) from one location to another very smoothly. I am very near to do this. I want to do something like this link in API v3.
Now, I have got all the lat/long from two locations, but still no smooth effect. Then I decided to get all lat/long between two lat/long, for example I got 1000 lat/long between two locations. Now I want lat/long between the first lat/long to the second lat/long of that 1000 lat/longs, that gives me more lat/long. Here is my code.
var map, ren, ser;
var data = {};
var marker;
function goma() {
map = new google.maps.Map(document.getElementById('mappy'), {'zoom':6, 'mapTypeId': google.maps.MapTypeId.ROADMAP, 'center': new google.maps.LatLng(22.05678288577881, 72.30236816615798)});
ren = new google.maps.DirectionsRenderer( {'draggable':true} );
ren.setMap(map);
ser = new google.maps.DirectionsService();
google.maps.event.addListener(ren, 'directions_changed', function(event){
var waypoints = ren.directions.routes[0].legs[0].via_waypoints;
});
ser.route({
'origin': new google.maps.LatLng(22.3000, 70.7800),
'destination': new google.maps.LatLng(23.0333, 72.6167),
'travelMode': google.maps.DirectionsTravelMode.DRIVING},
function(res,sts) {
if(sts=='OK')
ren.setDirections(res);
})
}
function run(i,j) {
if(i!=0 || j!=0) {
marker.setMap(null);
}
if(i<=ren.directions.routes[0].legs[0].steps.length) {
if(j==ren.directions.routes[0].legs[0].steps[i].path.length) {
j=0;
i++;
} else {
j++;
}
}
marker = new google.maps.Marker({
position: ren.directions.routes[0].legs[0].steps[i].path[j],
map: map
});
marker.setMap(map);
setTimeout(function() {run(i,j);}, 2000);
}
It works but not as I want. I just want smooth effect and also I'd like to find the distance I have passed and speed. Thank you.