I have a full page map, and what I would like to do is fix a polygon/rectangle over a portion of the map like such, so that the polygon will remain in the same place even if, say, the map was dragged. Then, I would like to set the center of the polygon to whatever the search query might be. Here is another image to illustrate what I mean. Unfortunately, from the documentation it seems as if you can only create polygons in another layer that remain attached, so to speak, to specific tiles on the base map. In addition to that, aside from setting its bounds, the documentation does not list any methods to set the center of a polygon.
Thus I have a couple of questions:
Is it even possible to fix a polygon/rectangle on the map, not in relation to but, independent of the positions of map tiles in the base layer?
Is it possible to set the center of a polygon/rectangle to a LatLng Object (or something similar), and then use this orientation to position the map as a whole?
P.S. If you're going to downvote my question, at least state why and give me a chance to address whatever your issue with the question might be...
I would recommend absolute positioning a div like this
<div id="mapdiv">
<div id="rect"></div>
</div>
and the css
#mapdiv {
position:relative;
width:600px;
height:400px;
background-color:#333;
}
#rect {
position:absolute;
top: 10%;
left: 10%;
width:80%;
height:80%;
background-color:#FFF;
}
as for using the co-ordinates and polys through the map. I have some experience with routes and such and would have to say it possible to get the shape but you would require a map refresh each time and for absolute positioning it. Not sure if thats possible
Sorry for that last answer...misunderstood. Have you tried this plus adding an offset of window width
function polygonCenter(poly) {
var lowx,
highx,
lowy,
highy,
lats = [],
lngs = [],
vertices = poly.getPath();
for(var i=0; i<vertices.length; i++) {
lngs.push(vertices.getAt(i).lng());
lats.push(vertices.getAt(i).lat());
}
lats.sort();
lngs.sort();
lowx = lats[0];
highx = lats[vertices.length - 1];
lowy = lngs[0];
highy = lngs[vertices.length - 1];
center_x = lowx + ((highx-lowx) / 2);
center_y = lowy + ((highy - lowy) / 2);
return (new google.maps.LatLng(center_x, center_y));
}
Again as for absolute positioning it after that I am still not quite certain
Might be a good starting point tho
Edit: found this link here on stack should actually work without knowing your setup how-do-i-get-google-maps-to-show-a-whole-polygon
Related
I have tried this couple of ways but have not been able to get it working. I want to place clocks at the top of the map within multiple timezones. I have a the javascript to create the clock and I place the clock in a div element.
Here is what I tried:
Create a Point with 0,0 coordinates.
From this point get the latitude value for the top of the map using containerPointToLatLng.
Create LatLng using the above lat and long for the timezone.
Converted this LatLng to Point and then positioning the div element with the x,y from this point.
I execute the logic both when the page is first rendered and then on body resize. However, if I change the size of the browser window, the clock does not position correctly.
Any suggestions?
To answer OP's precise issue, i.e. how to re-position the clock when the browser window is resized (hence map container dimensions may have changed), one should probably just re-compute the clock position on map's "resize" event.
However, it is not clear whether OP placed the clock as a child of the map container, or somewhere else on the page DOM tree.
It is probably much easier to place it as a child of the map, so that its position is always relative to the map container.
What OP originally asked?
If I understand correctly the original desired result, the OP would like to overlay a clock (or whatever information) on top of a particular geographical position (Toronto city in that case [UTC -5], according to comments).
However, the information container should not lay at a basemap fixed position, i.e. not at a precise geographic coordinated point (like a marker or a popup would), but at the top of the map container, similarly to a Control (hence iH8's original answer).
Except that it should not be totally fixed within the map container, but horizontally move with the city (or whatever specified geographical coordinates). Hence OP's comment to iH8's answer.
Therefore it sounds like something similar to that site, except with an interactive (navigate-able) map and the "UTC-5" header replaced by a clock (or whatever information, hence an HTML container should do it) and horizontally following Toronto.
Put differently, the clock should sit at a particular vertical line, i.e. longitude / meridian.
Unfortunately, even 2 years and a half after the question is posted, there is still no Leaflet plugin that provides such functionality (at least within Leaflet plugins page).
Extending the use case to highly zoomed-in map…
That being said, and given the fact that the user may be able to zoom highly into the city (OP did not specify the maximum zoom level), it might not be a very good user experience having that clock horizontally follow a precise longitude: for example, it could track Toronto centroid / city hall / whatever particular place, and when user is zoomed-in at another city district, the clock is no longer visible, whereas he/she is still viewing a part of Toronto city…
To extend that use case, the clock should very probably be visible in whatever area it applies, i.e. as soon as the map view port intersects the associated time zone.
Extending the use case to highly zoomed-out map…
Another point not detailed by OP, is what to do when places of different time zones are visible in the map view port? In the above mentioned site, we have one header per visible time zone, which seems the most complete information we can get.
But since Leaflet allows to zoom out down to level 0, where the entire world (i.e. essentially 24 time zones / actually 39 according to Wikipedia, not including potential effect of Daylight Saving Time - DST) is represented with a 256 pixels width, there is little room to fit all these clocks, if each one must be vertically aligned with its associated time zone.
For now let's assume we do not care if clocks overlap.
Even more custom case…
But OP may have wished to display the clock only for particular places, not for the entire world. OP did not even say that clocks would be different (we could have clocks for cities in the same time zone, even though it could be more interesting to have these clocks sit next to their city - even on par with their latitude, so that it is easier to spot which city the clock is associated to, like in the case of 2 cities on the same meridian; but in that case, a marker with L.divIcon would be enough).
Hence a custom case would be not to consider official time zones, but developer's specified areas.
So we forget about the latitude and try to align a clock vertically above the area, as long as it intersects the map view port.
Describing a generic solution
Therefore it sounds like a generic solution would be to enable the application developer to specify an array of HTML Elements, each one associated with a range of longitudes (could also be an area / polygon).
The time zones use case would then be a particular case where the specified areas are simply those from the time zones.
Then, each Element should be visible if and only if its associated area intersects the view port (therefore we introduce a possibility to hide it when the latitude range is out of view).
As for positioning, let's choose:
By the top of the map container (similar to a Control), as mentioned by OP.
Horizontally centered within the intersection of the view port and of the associated area.
HTML:
<div id="map"></div>
<div id="clockToronto" class="clock leaflet-control">Clock here for Toronto</div>
<div id="clockBurlington" class="clock leaflet-control">Clock here for Burlington</div>
CSS:
.clock {
width: 150px;
text-align: center;
position: absolute;
border: 1px solid black;
}
#clockToronto {
background-color: yellow;
}
#clockBurlington {
background-color: orange;
}
JavaScript:
var map = L.map("map").setView([43.7, -79.4], 10);
// Application specific.
var clockTorontoElement = L.DomUtil.get("clockToronto"),
clockBurlingtonElement = L.DomUtil.get("clockBurlington"),
zones = [
{
element: clockTorontoElement, // Using the HTML Element for now.
width: parseInt(window.getComputedStyle(clockTorontoElement, null).getPropertyValue("width"), 10),
area: L.latLngBounds([43.58, -79.64], [43.86, -79.10]) // Using L.latLngBounds for now.
},
{
element: clockBurlingtonElement, // Using the HTML Element for now.
width: parseInt(window.getComputedStyle(clockBurlingtonElement, null).getPropertyValue("width"), 10),
area: L.latLngBounds([43.28, -79.96], [43.48, -79.71]) // Using L.latLngBounds for now.
}
];
// Initialization
var controlsContainer = map._container.getElementsByClassName("leaflet-control-container")[0],
firstCorner = controlsContainer.firstChild,
mapContainerWidth;
map.on("resize", setMapContainerWidth);
setMapContainerWidth();
// Applying the zones.
for (var i = 0; i < zones.length; i += 1) {
setZone(zones[i]);
}
function setZone(zoneData) {
// Visualize the area.
L.rectangle(zoneData.area).addTo(map);
console.log("width: " + zoneData.width);
controlsContainer.insertBefore(zoneData.element, firstCorner);
map.on("move resize", function () {
updateZone(zoneData);
});
updateZone(zoneData);
}
function updateZone(zoneData) {
var mapBounds = map.getBounds(),
zoneArea = zoneData.area,
style = zoneData.element.style;
if (mapBounds.intersects(zoneArea)) {
style.display = "block";
var hcenterLng = getIntersectionHorizontalCenter(mapBounds, zoneArea),
hcenter = isNaN(hcenterLng) ? 0 : map.latLngToContainerPoint([0, hcenterLng]).x;
// Update Element position.
// Could be refined to keep the entire Element visible, rather than cropping it.
style.left = (hcenter - (zoneData.width / 2)) + "px";
} else {
style.display = "none";
}
}
function getIntersectionHorizontalCenter(bounds1, bounds2) {
var west1 = bounds1.getWest(),
west2 = bounds2.getWest(),
westIn = west1 < west2 ? west2 : west1,
east1 = bounds1.getEast(),
east2 = bounds2.getEast(),
eastIn = east1 < east2 ? east1 : east2;
return (westIn + eastIn) / 2;
}
function setMapContainerWidth() {
mapContainerWidth = map.getSize().x;
}
Live demo: http://plnkr.co/edit/V2pvcva5S9OZ2N7LlI8r?p=preview
Usually one would use L.Control to create a custom control which you can then add to the control layer. If you do so, leaflet will take care of positioning when resizing the map. Take a look at the reference for L.Control: http://leafletjs.com/reference.html#control
There is an example of a custom control in the reference: http://leafletjs.com/reference.html#icontrol If you would like to see more examples you could check out one of the many custom control plugins to see how they implemented L.Control: http://leafletjs.com/plugins.html (under Controls and interaction)
The only drawback of L.Control is that you can't position a control vertically or horizontally centered. You may only use topleft, topright, bottomleft & bottomright.
This question is directed to Leaflet users (and those who use the Leaflet.draw plugin)...
I'm using Leaflet and would like to allow my user to draw 1--and only 1--single polygon over any area of the map. I would also like to limit the size of that polygon in some way (such as limiting the length of the side for a square or the area covered it covers--preferably specified in degrees so that the set size limits would translate regardless of the zoom level).
My end goal is simply to extract the coordinates of the 4 square vertices or the coordinates covered by the polygon area.
That said, I found the Leaflet.Draw plugin. It is fantastic, however, I need to limit its functionality to my requirements (only 1 polygon drawn at a time and, in particular, the size cannot be drawn too large). Is this possible to do? If so, how?
Regardless of if it is or is not possible, is there a better way to go about doing this?
Can I propose another solution to this issue?
I would limit the number of polygons to one by doing the following:
map.on('draw:created', function (e) {
var layer = e.layer;
if(drawnItems && drawnItems.getLayers().length!==0){
drawnItems.clearLayers();
}
drawnItems.addLayer(layer);
});
I am listening to the draw:created event and determine if there is already a marker. If there is, I remove that marker and place my new one in the desired location. Therefore, one less click for user as they no longer need to delete the previous and one marker rule is always enforced.
If you wanted to allow more than one marker you could do a FIFO delete of the oldest layer.
If you do not want to automatically delete a layer, you could either prompt the user or ignore the request.
That said, I found the Leaflet.Draw plugin. It is fantastic, however, I need to limit its functionality to my requirements (only 1 polygon drawn at a time and, in particular, the size cannot be drawn too large). Is this possible to do? If so, how?
I think you'll need to code it yourself.
I see two possibities:
hacking the draw plugin (writing your own code inside the plugin)
extending the L.Draw.Polygon class from the draw plugin (see the docs about OOP in Leaflet) to create a costum one
1 is faster, 2 is cleaner. You'll have to choose depending on the size of your project.
I did it without hacking the Leaflet Draw source.
After the controls are added to the map, I place a hidden div inside the controls. Then when a polygon is created I display that div. I used CSS to absolute position it over the controls so the buttons are then "disabled" and CSS to make the buttons look faded. If the polygon is deleted then I hide that div.
Not the best solution, but I works without having to edit the source.
After drawControl is added, I add the hidden div:
$('.leaflet-draw-section:first').append('<div class="leaflet-draw-inner-toolbar" title="Polygon already added"></div>');
Here's the JS to toggle them:
map.on('draw:created', function (e) {
var type = e.layerType,
layer = e.layer;
// keep the polygon on the map
drawnItems.addLayer(layer);
// disable the create polygon tools
$('.leaflet-draw-inner-toolbar').show();
});
map.on('draw:deleted', function(e) {
// enable the create polygon tools
$('.leaflet-draw-inner-toolbar').hide();
});
Here's the CSS:
.leaflet-draw-inner-toolbar {
background: none repeat scroll 0 0 rgba(255, 255, 255, 0.6);
bottom: 0;
display: none;
left: 0;
position: absolute;
right: 0;
top: 0;
}
I'm having a bit of an issue trying to rotate an image on a particular point.
I've been using http://jsfiddle.net/YKj5D/ as an example; which works perfectly when wanting to rotate an image from its center point.
However, using the example above, i wanted to rotate the image around the letter G, how could this be achieved?
Current code:
function rotateGearStick()
{
var rotation = function (){
$("#gear-stick").rotate({
angle:0,
animateTo:110,
});
}
rotation();
}
Where gear-stick is the ID of my image.
I'm attempting to create an rev counter dial.
http://jsfiddle.net/YKj5D/1988/
#image{
margin:100px;
padding-left:170px; /* <----- */
}
I find this a lot more cleaner fix.
#image{
-webkit-transform-origin:0px 0px;
-moz-transform-origin:0px 0px;
-ms-transform-origin:0px 0px;
transform-origin:0px 0px; //px or percent whichever you prefer
}
Just change the origin values to whatever suits your needs and it will rotate about that origin.
Recent version of jQueryRotate handles changing center of rotation cross-browse. Using padding-only solution will not work for a fallbacks to CANVAS/VML. Please check http://jqueryrotate.googlecode.com
The http://jqueryrotate.googlecode.com (suggested by Wilq32 in above post) documentation says adding the "center" attribute as shown below. It takes a array of length 2 of absolute pixel position or percentage. I.e. center: ["150px", "100px"] OR center: ["60%", "50%"]
var rotation = function (){
$("#image").rotate({
angle:0,
animateTo:360,
center: ["150px", "100px"],
callback: rotation
});
}
rotation();
I have the following Google Map test app: http://dev.driz.co.uk/googlemap/
As you can see I use geolocation to show your (the user) position on the map, and then some JSON data to populate the map with other markers.
Note: depending where you are in the world you may not see the pins (they are in the UK near Huddersfield) if you zoom out you should seem them.
I am having the following issues:
1.) All the markers have the same titles, so I'm presuming that somewhere in the for loop at the bottom of the page I have made a mistake... Not sure what though?
Fixed in answers below.
2.) The markers have various overlapping issues due to the z-index and also because some of the markers have the same co-ordinates. Is it possible to make it so that markers offset themselves a couple pixels per loop so that they don't overlap, and the z-index automatically increases per loop so they are higher than the previous marker
Need to make it so that when a user hovers the marker it has a higher z-index to make it sit on top... If that makes sense? So in the hover event I need to get the latest offset and then add to that to make it the highest! But how do I alter the zindex of the marker on the hover?
3.) The final thing (and probably the most difficult) is that the tooltips are not equally positioned when moved to the right side of the marker when the map is moved. Any ideas to improve this? They get even worse with the JSON-based markers and slip off the map.
Can anyone help me out with these problems?
Thanks
I don't know if this will work, but its following the pattern of that link I shared, perhaps something like this....
function doToolTip(item) {
return function () {
mTooltip = new Tooltip('<span class="name">' + item.User.Profile.firstname + ' asked:</span> <span class="title">' + item.Post.title + '</span>');
mTooltip.open(this.getMap(), this);
};
}
...and this is your main code. I think 'item needs' initialising outside the scope of the loop (but I could be wrong)
//other code etc...
var item;
for (var i = 0; i < data.length; i++) {
item = data[i];
//other code etc....
google.maps.event.addListener(marker, 'mouseover', doToolTip(item));
//other code etc...
}
OK. I'm guessing here, as I haven't got a local copy of the code, but, It looks like you need to change the z-index when you do the draw function...
//code fragment...
// need to force redraw otherwise it will decide to draw after we show the Tooltip
$(this).css('z-index', 9999);
this.draw();
// show tooltip
With regard to the position of the tooltip, you're going to have to experiment with the draw function, as it seems to calculate the position from the marker. It might be better to work out the position not from the google map coordinates but from the actual position on the page - I think the culprits are:
pos = this.getProjection().fromLatLngToDivPixel(this.get('position'));
// top offset
top = pos.y - this.getAnchorHeight() / 2 - this.wdiv.outerHeight() / 2;
// left offset
if (this.getMap().getCenter().lng() > this.get('position').lng()) {
left = pos.x + this.wdiv.outerWidth();
} else {
left = pos.x - this.wdiv.outerWidth();
}
// window position
this.wdiv.css('top', top);
this.wdiv.css('left', left);
If the positioning is consistently off, you could just apply a correction to the top and left values, if it's more complicated, you'll have to change the algorithm.
I am trying to get my custom markers to show up on my map after i have used the fitBounds() method to fit the boundaries of the map to the markers themselves.
I have done this by looping over the markers array and then extending the boundaries of the map to incorporate the marker co-ordinates.
This works fine with the stock google maps markers. However, my client wants to use quite large (36px by 57px) marker images for their site. How do i compensate for this when fitting the boundaries of the map?
At the moment when using the custom marker images they do not all fit inside the boundaries set.
Since you already have calculated the bounds, you may just need to extend the bounds to add enough buffer area to include the large images. The formula you can use to calculate or extend a bounds this way is called a convex hull; the Computational Geometry Algorithms Library has a section on 2D Convex Hull Algorithms or there is a JavaScript Quickhull Article that also includes a nifty online example near the bottom of the page. Hope this is helpful -
The cheap answer is to always zoom out one level after fitBounds(). But we can do a bit better.
I like writing hacks. Here I am making the assumption that the size of your marker will never be larger than 36x57. I tested a while back to find that fitBounds() leaves a margin of around 42 px between the edge and the closest marker (maybe not on mobiles), and I'm also assuming you are not repositioning the marker, that is, it will always be displayed above the given coordinate position. If icons run off to the other sides, adjustments are needed.
My hack takes advantage of a function that measures the pixel position of a LatLng (using the container version, I read here that the div version is not reliable with bounds changes).
Since we know the height of the icon, and where the topmost marker is, we can pan the map south a bit if it's determined to be offscreen. In case there's not enough margin below, the only option is to zoom out. My only concern is it will be jerky because it calls for two events: fitBounds and the custom panning/zooming. The only answer then would be to rewrite a custom fitBounds. When I tested manually the events ran smoothly.
http://jsfiddle.net/sZJjY/
Click to add cat icons, right-click to trigger the resize/panning.
Example: place 3-4 kitties, right-click, then purposely place another that goes off the top, right-click again.
function fitIcons() {
var left = 180.0;
var right = -180.0;
var top = -90.0;
var bottom = 90.0;
for (var i = 0; i < markers.length; i++) {
curlat = markers[i].getPosition().lat();
curlng = markers[i].getPosition().lng();
if(curlat > top) { top = curlat; }
if(curlat < bottom) { bottom = curlat; }
if(curlng > right) { right = curlng; }
if(curlng < left) { left = curlng; }
}
var overlay = new google.maps.OverlayView();
overlay.draw = function() {};
overlay.setMap(map);
map.fitBounds(new google.maps.LatLngBounds(
new google.maps.LatLng(bottom, left),
new google.maps.LatLng(top, right)));
topPixels = overlay.getProjection().fromLatLngToContainerPixel(
new google.maps.LatLng(top, right));
bottomPixels = overlay.getProjection().fromLatLngToContainerPixel(
new google.maps.LatLng(bottom, left));
topGap = topPixels.y;
bottomGap = $("#map_canvas").height() - bottomPixels.y;
if(topGap < iconHeight) {
if(bottomGap > iconHeight) {
map.panBy(0, topGap);
}
else {
map.setZoom(map.getZoom() - 1);
}
}
}