Google Maps Draw -- draw line or polygon by dragging - javascript

Currently, you can draw polygons or polylines on a map by clicking, which creates a node. However, if you wanted to follow a feature such as a river or shoreline, this could be both tedious and innacurate.
Does anyone know of a way where a path could be drawn either by click-dragging the mouse, or by clicking to start, then dragging a path, then clicking to stop?
I've looked at the DrawingManager and MouseEvent, but I'm not seeing anything promising yet.
Is there a way to wire up drawing based on events such as click and mouseMove?
This would allow for much more accurate and quick features.

Possible workflow:
onmousedown on the map set the draggable-option of the map to false, create a Polyline-Instance and set the clickable-option of the Polyline to false
observe the mousemove-event of the map, each time it occurs push the returned LatLng to the path of the Polyline
onmouseup remove the mousemove-listener for the map and set the draggable-option of the map back to true. Your Polyline has been finished
When you wan't to create a Polygon, create now a Polygon-instance, set the path of the Polygon to the path of the Polyline and remove the Polyline
<edit>:
It's recommendable to draw only with a pressed right mousebutton, otherwise the map would never be draggable.
Demo: http://jsfiddle.net/doktormolle/YsQdh/
code snippet: (from linked jsfiddle)
function initialize() {
var mapOptions = {
zoom: 14,
center: new google.maps.LatLng(52.5498783, 13.425209099999961),
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById('map_canvas'),
mapOptions);
google.maps.event.addDomListener(map.getDiv(), 'mousedown', function(e) {
//do it with the right mouse-button only
if (e.button != 2) return;
//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);
if (document.getElementById('overlay').value == 'Polygon') {
var path = poly.getPath();
poly.setMap(null);
poly = new google.maps.Polygon({
map: map,
path: path
});
}
});
});
}
google.maps.event.addDomListener(window, 'load', initialize);
html,
body {
height: 100%;
margin: 0
}
#map_canvas {
height: 90%;
}
<script src="https://maps.googleapis.com/maps/api/js"></script>
Use the right mouse-button to draw an overlay
<br/>
<select id="overlay">
<option value="Polyline">Polyline</option>
<option value="Polygon">Polygon</option>
</select>
<div id="map_canvas"></div>

Related

How to change the default Marker used in the Google Maps API Drawing Layer controls

I would like to know how could I use a custom marker with google maps drawing controls. I want to do that because I need to place a marker when a user clicks in the map and have it open a info window (when clicked) and have a few custom actions in there (buttons and such).
I'm using react and #react-google-maps/api for this, but that might be besides the point since it is just a wrapper around the Maps Javascript API provided by Google.
From the docs it is possible to provide google.maps.MarkerOptions to the google.maps.drawing.DrawingManagerOptions. Unfortunately there is no option there to provide a custom Marker to be rendered.
I tried using the markercomplete() call back exposed by the google.maps.drawing.DrawingManager since it has the newly created Marker as parameter, and then doing something like this:
const handleNewMarker = (marker) => {
marker.addListener('click', function() {
setActiveMarker(marker);
});
}
My map component would then be something like this:
<GoogleMap
zoom={18}
center={latLng}
>
{activeMarker && <CustomInfoWindow anchor={activeMarker} />}
<DrawingManager
options={{
markerOptions: {
clickable: true,
draggable: true,
},
}}
onMarkerComplete={handleNewMarker}
/>
</GoogleMap>
Although this worked, it is not at all viable for production, for some reason the InfoWindow takes too much time to appear in the screen, that approach might be causing a memory leak and I don`t know why.
I might be missing something here, but on my research I didn't find a single soul trying to create a custom marker with the drawing tool, just custom markers by themselves which is relatively easy to do. My ideal case scenario, since I'm using React, would be to create a CustomMarker component with a CustomInfoWindow inside it, and just tell the drawing controls, "hey, take this marker and use it whenever a user tries to draw a new marker with your drawing tool".
Thank you.
Edit
Here is a screenshot of what I mean, that marker in the screen shot was placed there using the "new marker" drawing control, and I need the "new marker" drawing control to place a custom marker defined by me.
All you need to do is to set the icon property in the MarkerOptions, if I correctly understand your meaning of "custom marker". Below is an example using a SVG path for the icon.
This snippet is in full JS but the same should work with the React library.
var map;
function initialize() {
var mapOptions = {
center: new google.maps.LatLng(-34.397, 150.644),
zoom: 8
};
map = new google.maps.Map(document.getElementById('map'), mapOptions);
var drawingManager = new google.maps.drawing.DrawingManager({
drawingControl: true,
drawingControlOptions: {
position: google.maps.ControlPosition.TOP_CENTER,
drawingModes: [google.maps.drawing.OverlayType.MARKER]
},
markerOptions: {
draggable: false,
icon: {
path: "M-20,0a20,20 0 1,0 40,0a20,20 0 1,0 -40,0",
fillColor: '#FF0000',
fillOpacity: 0.6,
anchor: new google.maps.Point(0, 0),
strokeWeight: 0,
scale: 1
}
}
});
drawingManager.setMap(map);
}
#map {
height: 180px;
}
<div id="map"></div>
<!-- Replace the value of the key parameter with your own API key. -->
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=initialize&libraries=drawing" async defer></script>
Now if you need the marker to be clickable, have an Infowindow, etc. do you really need to use the drawing manager? Could you not simply listen for a map click event, and create a standard marker?
Or possibly, use both? (Create the marker with the drawing manager and convert it to a standard marker by the use of the markercomplete event, which seems to be more or less what you are doing.)
Edit:
If you need to create a "real" marker with an InfoWindow, you can do it in the markercomplete event.
var map;
var infowindow;
function initialize() {
var mapOptions = {
center: new google.maps.LatLng(-34.397, 150.644),
zoom: 8
};
map = new google.maps.Map(document.getElementById('map'), mapOptions);
infowindow = new google.maps.InfoWindow();
var drawingManager = new google.maps.drawing.DrawingManager({
drawingControl: true,
drawingControlOptions: {
position: google.maps.ControlPosition.TOP_CENTER,
drawingModes: [google.maps.drawing.OverlayType.MARKER]
}
});
drawingManager.setMap(map);
google.maps.event.addListener(drawingManager, 'markercomplete', function(marker) {
// Remove overlay from map
marker.setMap(null); // Optional, but this will remove the drawn marker
drawingManager.setDrawingMode(null); // Optional, but this will "disable" the drawing tools
// Create the "real" marker
createMarker(marker.getPosition());
});
}
function createMarker(position) {
var marker = new google.maps.Marker({
position: position,
map: map,
title: 'Custom marker',
icon: {
path: "M-20,0a20,20 0 1,0 40,0a20,20 0 1,0 -40,0",
fillColor: '#FF0000',
fillOpacity: 0.6,
anchor: new google.maps.Point(0, 0),
strokeWeight: 0,
scale: 1
}
});
google.maps.event.addListener(marker, 'click', function() {
infowindow.setContent('This is the content');
infowindow.open(map, this);
});
}
#map {
height: 180px;
}
<div id="map"></div>
<!-- Replace the value of the key parameter with your own API key. -->
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=initialize&libraries=drawing" async defer></script>

Wrong position of infowindow on small zoom levels. Google maps api v3

On small zoom levels one place will be displayed twice or more (world map is repeated horizontally). When I place marker on map with coordinates (e.g. -32.05604, 115.74718) I will see it on map more than once. It's normal.
But if I try to show infowindow on mouse hover I will get wrong behaviour. Infowindow will be always showed near one of my markers and never near other, without relation what marker was under mouse cursor.
See example here: (try to move mouse to left marker on map).
https://jsfiddle.net/m3wpk1gr/1/
How to show infowindow for marker which under mouse?
My code:
function initialize() {
var myLatLng = new google.maps.LatLng(-32.05604, 115.74718),
myOptions = {
zoom: 1,
center: new google.maps.LatLng(-32.05604, 0)
},
map = new google.maps.Map(document.getElementById('map-canvas'), myOptions),
marker = new google.maps.Marker({
position: myLatLng,
map: map
});
marker.setMap(map);
var iw = new google.maps.InfoWindow();
marker.addListener('mouseover', function() {
iw.setContent('InfoWindow');
iw.open(map, this);
});
marker.addListener('mouseout', function() {
iw.close();
});
}
initialize();
I'm afraid there is nothing you can do to get the desired result(the position is correct, so when the position exists multiple times on a map, the API has to make a decision).
What you can do: set the optimized-option of the marker to false, then they will not be repeated at lower zoom-levels

Mouseover and mouseout for rectangle are not fired correctly in Google Maps API v3

I have a map, and I draw a rectangle on it. Here is what I want: whenever my mouse enter that rectangle, open an infowindow, when my mouse leave, close the infowindow.
I have successfully created a map and drawn a rectangle. Here is my code:
var map;
function initialize() {
// Init map
var mapOptions = {
center: { lat: ***, lng: *** },
zoom: 13,
draggable: false,
scrollwheel: false,
zoomControl: false,
streetViewControl: false,
mapTypeControl: false,
disableDoubleClickZoom: true
};
map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
google.maps.event.addListenerOnce(map, 'bounds_changed', function () {
drawGrid();
});
} // end initialize
function drawGrid() {
var rectangle = new google.maps.Rectangle({
strokeColor: '#000',
strokeWeight: 2,
fillOpacity: 0,
map: map,
bounds: new google.maps.LatLngBounds(sw1, ne1) //sw1 and ne1 is my variable
});
var infowindow = new google.maps.InfoWindow({
content: "Hello",
position: rectangle.getBounds().getCenter()
});
google.maps.event.addListener(rectangle, 'mouseover', function () {
infowindow.open(map);
});
google.maps.event.addListener(rectangle, 'mouseout', function () {
infowindow.close();
});
}
google.maps.event.addDomListener(window, 'load', initialize);
You can see the result below. The rectangle is the black one.
Now, the problems are:
Mouseover event is fired only when I let my mouse follow the green arrows.
Mouseout event is fired only when my mouse follow the green arrows (yes, from out to in, not in to out as expected), and it's fired twice.
Why do I encounter those problems? What did I do wrong? How to solve this problem? Thank you so much for your help.
There was a change to the way Firefox 39.0 handles mouse events. See issue 8278 in the issue tracker
Project Member #4 enoch...#google.com
Thanks for reporting this issue. Indeed, it appears that Firefox 39 has made changes to its mouse event, which is causing the API to behave incorrectly.
Status: Accepted
Owner: enoch...#google.com
Labels: Internal-20820906
Note that users have reported that v=3 (the release version) is working correctly and this issue only appears in v=3.exp (the "experimental" version).

Shapes don't respect map's 'draggable' property

Edge scrolling
Let's say I've created a simple map with default options, a draggable marker and a draggable shape (I've tested it with circle, rectangle, polygon and polyline). When I drag the marker or shape to the edge of map canvas, the map starts scrolling and that's fine.
JSFiddle demo
The problem
When I set map's draggable property to false, the edge scrolling doesn't work anymore with the marker, but all the shapes ignore it and the map is still scrolled - and that's unexpected behaviour.
var mapOptions = {
...
draggable: false
};
JSFiddle demo
The problem exists in both release (3.16) and experimental (3.17) versions. I've reported it on google's bugtracker, but it will probably take years before they fix it. So, does anyone know a workaround?
Edit: for the sake of simplicity, let's assume that zooming the map is also disabled
You will need to listen to the map zoom_changed, bounds_changed and the circle dragend events.
If the shape is dragged outside of the defined bounds, set it back to its last position and set the map center to the original value.
function initialize() {
var bounds = null;
var zoomLevel = 15;
var center = new google.maps.LatLng(20.291, 153.027);
var mapOptions = {
zoom: zoomLevel,
center: center,
mapTypeId: google.maps.MapTypeId.TERRAIN,
draggable: false
};
var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
var marker = new google.maps.Marker({
map: map,
position: center,
draggable: true
});
var circlePos = new google.maps.LatLng(20.281, 153.037);
var circle = new google.maps.Circle({
map: map,
center: circlePos,
radius: 100,
strokeWeight: 1,
fillColor: '#000000',
fillOpacity: 1,
draggable: true
});
google.maps.event.addListener(map, 'zoom_changed', function () {
// If user changes zoome level, get the new level and the new map bounds
zoomLevel = map.getZoom();
bounds = map.getBounds();
});
google.maps.event.addListener(map, 'bounds_changed', function () {
// Get the initial map bounds if not set yet
if (!bounds) {
bounds = map.getBounds();
}
});
google.maps.event.addListener(circle, 'dragend', function () {
var position = circle.getCenter();
if (bounds.contains(position)) {
// Update the circle position
circlePos = position;
} else {
// Set the circle back to its last position
// Reset the map center as it might have changed
circle.setCenter(circlePos);
map.setCenter(center);
}
});
};
JSFiddle demo
Kindly note that you can eliminate the zoom_changed event listener if you disable the zooming options on your map. Problem if a user can use the zoom is that your shape can still disappear from the map bounds if the user zooms in, which can be confusing.
Google fixed it in version 3.19, it now works properly.

ArcGIS simple example dynamically rendering a marker

I am having a hard time trying to add a simple clickable marker to an ArcGIS, map purely using JavaScript. All of the ArcGIS Samples seem to get their marker and related popup information from the server. How can I achieve the same result with ArcGIS as this Google Maps sample code below?
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<div id="map" style="width: 500px; height: 500px;"></div>
<script type="text/javascript">
window.onload = function() {
var myOptions = {
zoom: 2,
center: new google.maps.LatLng(40, -75),
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("map"), myOptions);
var icon = new google.maps.MarkerImage("http://cinnamonthoughts.org/wp-content/uploads/2010/01/Custom-Marker-Avatar.png");
var markerOptions = {
icon: icon,
map: map,
position: new google.maps.LatLng(37.7699298, -122.4469157),
};
var marker = new google.maps.Marker(markerOptions);
google.maps.event.addListener(marker, 'click', function() {
var infoWindow = new google.maps.InfoWindow();
infoWindow.setContent("hello world");
infoWindow.open(map, marker);
});
};
</script>
Try this:
Create a Point for you longitude and latitude
Convert the point to Web Mercator spatial reference, if it's necessary
Create a PictureMarkerSymbol for you custom picture marker
Create a Graphic using the point and the symbol
Create a GraphicsLayer
Add the graphic to the graphic layer
Add the graphic layer to your map
Add a custom onClick event listener to your layer
Some equivalent code:
var point = new esri.geometry.Point(longitude, latitude);
point = esri.geometry.geographicToWebMercator(point);
var symbol = new esri.symbol.PictureMarkerSymbol("marker.png", 32, 32);
var graphic = new esri.Graphic(point, symbol);
var layer = new esri.layers.GraphicsLayer();
layer.add(graphic);
map.addLayer(layer);
dojo.connect(layer, "onClick", onClick);
On the event listener you can open a custom infoWindow or whatever you like:
function onClick(event) {
map.infoWindow(...)
...
Change "marker.png" and 32x32 to use your custom marker image and dimensions.
Try placing your image icon anywhere on the screen X Y using the CSS positioning then just put your onClick handler inside the actual DIV or IMG tag. Try playing with combination relative vs. absolute to get it just right.
div#header {
position: relative;
}
img#headimg {
position: absolute;
left: whatever you like
top: whatever you like
}

Categories