First I'm pretty new to Javascript, so sorry if my question comes across poorly.
I'm creating an application in Flash to help users calculate their electrical costs. Then I'm taking this figure and write it to an xml file.
Now I'm looking to open a webpage and show a google map, and there is a rectangle drawn over the map which is generated dynamically from the number generated earlier and stored in the xml file.
I'm completely lost as to places to turn on how to achieve this. I've gotten my map on to my page, and it scales 100% as I want it to, but I can't figure out the dynamic rectangle part at all. Any ideas or pointers in the right direction greatly appreciated.
In this latest version, the XML file
<countries>
<country name="USA" lat="40.0" lng="-100.0" width="30.0"/>
<country name="France" lat="46.6" lng="2.7" width="10"/>
<country name="Germany" lat="51.1" lng="10.1" width="20"/>
</countries>
is loaded as soon as the map tiles finish loading. I could not get the getProjection to be called correctly if I did not wait for tile loading to finish. The docs state that getting the projection needs the map to be initialized, and recommends listening for projection_changed. Both ways work yet I still feel listening to tiles_loaded is safer and if something goes wrong with the xml loading it will get called again if the map is zoomed or panned a noticeable amount.
var map;
var xmlLoaded = false;
function initialize() {
var mapOptions = { center: new google.maps.LatLng(30.0, 0.0), zoom: 2,
mapTypeId: google.maps.MapTypeId.ROADMAP };
map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
google.maps.event.addListener(map, 'tilesloaded', loadData);
}
function loadData() {
if(!xmlLoaded) {
$.ajax({
type: "GET",
url: "co2data.xml",
dataType: "xml",
success: function(xml) {
var countries = xml.documentElement.getElementsByTagName("country");
for(var i = 0, country; country = countries[i]; i++) {
var name = country.getAttribute("name");
var lat = parseFloat(country.getAttribute("lat"));
var lng = parseFloat(country.getAttribute("lng"));
var point = map.getProjection().fromLatLngToPoint(new google.maps.LatLng(lat,lng));
// width is really an arbitrary unit, relative to CO2 tonnage.
// equals the side of the drawn square.
// it is measured in google maps points units.
var width = parseFloat(country.getAttribute("width"));
makeCO2Rect(name, point, width);
}
xmlLoaded = true;
}
});
}
}
The rectangle is defined by width in points (the whole world is 256x256 points), so some conversion is needed when assigning their centers to the more conventional LatLng.
function rectParamsToBounds(point, width) {
var ctrX = point.x;
var ctrY = point.y;
var swX = ctrX - (width/2);
var swY = ctrY - (width/2);
var neX = ctrX + (width/2);
var neY = ctrY + (width/2);
return new google.maps.LatLngBounds(
map.getProjection().fromPointToLatLng(new google.maps.Point(swX, swY)),
map.getProjection().fromPointToLatLng(new google.maps.Point(neX, neY)));
}
Finally, a rectangle is created with a country name that goes into a MarkerWithLabel (using v1.1.5 here, you can hotlink to http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerwithlabel/1.1.5/src/markerwithlabel_packed.js though I prefer saving a local copy)
Since dragging a rectangle appears impossible, a marker in its center works as a handle. When it's dragged, the associated rectangle moves with it.
function makeCO2Rect(name, point, width) {
var rect = new google.maps.Rectangle({
map: map,
bounds: rectParamsToBounds(point, width)
});
var marker = new MarkerWithLabel({
map: map,
position: map.getProjection().fromPointToLatLng(new google.maps.Point(point.x, point.y)),
draggable: true,
raiseOnDrag: false,
labelContent: name,
labelAnchor: new google.maps.Point(30, 0),
labelClass: "labels", // the CSS class for the label
labelStyle: {opacity: 1.0}
});
google.maps.event.addListener(marker, 'drag', function(event) {
var newLatLng = event.latLng;
var newPoint = map.getProjection().fromLatLngToPoint(newLatLng);
rect.setBounds(rectParamsToBounds(newPoint, width));
});
}
google.maps.event.addDomListener(window, 'load', initialize);
Styling the labels need to be done both in the .labels CSS class and the constructor, and rectangles have options like stroke color, thickness, opacity, and fill color.
If you just want to place a rectangular shape on the map, you can create a google.maps.Rectangleapi-doc. If you want to create a rectangular label on the map, you may be more interested in the InfoBox Utility Librarywiki-page.
Related
I want to add a marker in the middle of a polygon that is made form geojson data. The polygon is connected a control where the layer can be turned on and off. This marker should only be displayed when the layer is active. I have the following code:
var geoJsonLayer = L.geoJSON(Locations, {
onEachFeature: function (feature, layer) {
if (feature.geometry.type === "Polygon") {
var bounds = layer.getBounds();
var center = bounds.getCenter();
var markerTitle = feature.properties.ItemId;
layer.id = markerTitle;
var popUpFormat = dataPopUp(feature);
layer.bindPopup(popUpFormat, customPopUpOptions);
}
},
});
Thanks for your interest and I hope someone can help me :D
You want to group a L.Polygon and a L.Marker together, and treat them as the same entity. This is a textbook scenario for using L.LayerGroups, e.g.
var geoJsonLayer = L.geoJSON(Locations, {
onEachFeature: function (feature, layer) {
if (feature.geometry.type === "Polygon") {
var center = layer.getBounds().getCenter();
var marker = L.marker(center);
var polygonAndItsCenter = L.layerGroup([layer, marker]);
}
},
});
Now polygonAndItsCenter is a L.LayerGroup with the polygon and its center (so adding/removing to/from the map will apply to both), but geoJsonLayer will contain only the polygons. How you handle that is up to you, but I guess you might want to not add geoJson to the map (using only for parsing and instantiating the polygons), and keep track of your polygon+marker LayerGroups separately, e.g.
var polygonsWithCenters = L.layerGroup();
var geoJsonLayer = L.geoJSON(Locations, {
onEachFeature: function (feature, layer) {
if (feature.geometry.type === "Polygon") {
var center = layer.getBounds().getCenter();
var marker = L.marker(center);
var polygonAndItsCenter = L.layerGroup([layer, marker]);
polygonAndItsCenter.addTo(polygonsWithCenters);
}
},
});
// geoJsonLayer.addTo(map); // No!!
polygonsWithCenters.addTo(map);
// Do something with a polygon+marker, e.g. remove the first one from the map
polygonsWithCenters.getLayers()[0].remove();
There are a few secondary problems that can spawn for this, so think about what you want to do with each polygon/layergroup/marker before writing code, keep the Leaflet documentation at hand, and remember:
You can not attach events or bind popups to LayerGroups, but you can do that to L.FeatureGroups
The center of a polygon's bounding box is different from its centroid which is different from the point inside the polygon which is furthest away from any of its edges. Only the third option is guaranteed to be inside the polygon.
I have a Google Map inside a JavaScript Accordion UI, and it puts a pin in the passed latitude/longitude and "centers" on that pin.
For the first map, it works. When you click either of the other two, it seems to put the (base of the) pin in the top-left corner of the map. I'm already calling resize, as you can see in my code.
Here is a fiddle which demonstrates the issue: http://jsfiddle.net/myingling/1c4bjsff/2/
I'm sure this is something simple, but what am I missing?
You need to save the center of each map, then set it once the new accordion is activated (and that map has a size, when the map/accordion is hidden, it has a computed size of zero, so the marker is centered in the upper left hand corner of the div).
One option:
var maps = [];
jQuery("#accordion").accordion({
change: function(event, ui) {
for (var i = 0; i < maps.length; i++) {
google.maps.event.trigger(maps[i], 'resize');
maps[i].setCenter(maps[i]._center);
}
}
}).find('.map').each(function(i, o) {
var latLong = new google.maps.LatLng(
jQuery(this).data("lat"), jQuery(this).data("long"));
var map = new google.maps.Map(o, {
zoom: 12,
mapTypeId: google.maps.MapTypeId.ROAD,
center: latLong
});
maps.push(map);
var marker = new google.maps.Marker({
position: latLong,
map: map,
title: jQuery(this).data('time')
});
google.maps.event.trigger(map, 'resize');
map.setCenter(latLong);
map._center = latLong;
jQuery(o).data('map', map);
});
proof of concept fiddle
I use the following block of JavaScript to try to show a WMS layer. I'm using OpenLayers 2.8.
The map's base layer (Openstreetmap) shows correctly, it zooms to the correct area, the "pyramid" layer is shown in the layer switcher, but no request to its WMS service is ever made (so the fact that the URL, styles and params are dummies shouldn't matter -- it never even attempts to get them).
OpenLayers does try to get a WMS layer once I pan or zoom far enough so that the Gulf of Guinea is in view (but all my data is in the Netherlands). This suggests a projection problem (WGS84's (0, 0) point is there), but I don't understand why OpenLayers doesn't even try to fetch a map layer elsewhere. My data is in EPSG:3857 (Web Mercator) projection.
/*global $, OpenLayers */
(function () {
"use strict";
$(function () {
$(".map").each(function () {
var div = $(this);
var data_bounds = div.attr("data-bounds");
console.log("data_bounds: " + data_bounds);
if (data_bounds !== "") {
var map = new OpenLayers.Map(div.attr("id"), {
projection: "EPSG:3857"});
var extent = JSON.parse(data_bounds);
var bounds = new OpenLayers.Bounds(
extent.minx, extent.miny,
extent.maxx, extent.maxy);
map.addLayer(
new OpenLayers.Layer.OSM(
"OpenStreetMap NL",
"http://tile.openstreetmap.nl/tiles/${z}/${x}/${y}.png",
{buffer: 0}));
map.addLayer(
new OpenLayers.Layer.WMS(
"pyramid", "http://rasterserver.local:5000/wms", {
layers: "test",
styles: "test"
}, {
singleTile: true,
isBaseLayer: false,
displayInLayerSwitcher: true,
units: 'm'
}));
map.addControl(new OpenLayers.Control.LayerSwitcher());
map.zoomToExtent(bounds);
}
});
});
})();
Edit: the 'data_bounds' console print prints (with some added formatting):
data_bounds: {
"minx": 582918.5701295201,
"miny": 6923595.841021758,
"maxx": 821926.9006116659,
"maxy": 7079960.166533174
}
It zooms to the correct region in the north of the Netherlands, so I don't think the problem is there.
Since posting, I found out that if I don't use the OSM layer, and instead use the WMS layer as baselayer, it works. So perhaps there's some incompatibility with a OSM baselayer and a WMS layer added to it? But then I don't get that it does seem to do something near WGS84 (0, 0).
I eventually managed to fix this by giving the map an explicit maxExtent:
var extent = JSON.parse(data_bounds);
var bounds = new OpenLayers.Bounds(
extent.minx, extent.miny,
extent.maxx, extent.maxy);
var map = new OpenLayers.Map(div.attr("id"), {
projection: "EPSG:3857",
maxExtent: bounds
});
Oddly enough this doesn't limit the user's ability to pan and zoom around the world, but it does make the overlay work...
I'm using Google Maps API and jquery-ui-maps (this questions has nothing to do with the plugin which is working great).
I've created a FusionTablesLayer with all countries except Mozambique. The user could place a marker and reposition it. I'm trying to find a way to block the drag (or alert the user, it doesn't matter now) if he tries to place the marker outside Mozambique (over the FusionTablesLayer).
After some research I discover this method: containsLocation(point:LatLng, polygon:Polygon), which computes whether the given point lies inside the specified polygon.
It should receive a Polygon and I've got a FusionTablesLayer. Any clue how to solve this?
Here's my code:FIDDLE
Try to place a marker and drag it...
//Initialize the map
var mapa = $('#map_canvas').gmap({'center': '-18.646245,35.815918'});
$('#map_canvas').gmap('option', 'zoom', 7);
//create the layer (all countries except Mozambique)
var world_geometry;
$('#map_canvas').gmap().bind('init', function(event, map) {
world_geometry = new google.maps.FusionTablesLayer({
query: {
select: 'geometry',
from: '1N2LBk4JHwWpOY4d9fobIn27lfnZ5MDy-NoqqRpk',
where: "ISO_2DIGIT NOT EQUAL TO 'MZ'"
},
styles: [{
polygonOptions: {
fillColor: "#333333",
fillOpacity: 0.3
}
}],
map: map,
suppressInfoWindows: true
});
});
$('#map_canvas').gmap().bind('init', function(event, map) {
$(map).click(function(event) {
$('#map_canvas').gmap('clear', 'markers');
$('#map_canvas').gmap('addMarker', {
'position': event.latLng,
'draggable': true,
'bounds': false
}, function(map, marker) {
}).dragend(function(event) {
//I need to check if the marker is over the FusionTablesLayer and block the drag.
//var test = google.maps.geometry.poly.containsLocation(event.latLng, world_geometry);
}).click(function() {
})
});
});
Since there is no containsLocation in FusionTablesLayer, and since no mouseevents but click is supported (that would have made it a lot easier) - there is no other way round than to check if there is being dragged outside the area itself, Mozambique - not into the FusionTablesLayer. The solution is to create an invisible polygon for Mozambique, and use that polygon to check for containsLocation when dragging is finished.
The polygon can be based on the KML from the row you are excluding, MZ. That can be done using google.visualization.Query.
1) include the Google API loader in your project :
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
2) initialize Visualization :
google.load('visualization', '1.0');
3) define a variable for the polygon holding the Mozambique borders :
var mozambique;
The following is a function that loads the geometry data for Mozambique, and then creates an invisible polygon on the map; google.visualization.Query is used instead of the automated FusionTablesLayer so we can extract the <coordinates> from the KML and use them as base for the polygon.
In basic, this is how to convert KML-data from a FusionTable to a polygon :
function initMozambique(map) {
//init the query string, select mozambique borders
var sql = encodeURIComponent("SELECT 'geometry' FROM 1N2LBk4JHwWpOY4d9fobIn27lfnZ5MDy-NoqqRpk WHERE ISO_2DIGIT ='MZ'");
var query = new google.visualization.Query('http://www.google.com/fusiontables/gvizdata?tq=' + sql);
query.send(function (response) {
var data = response.getDataTable().getValue(0, 0);
//create a XML parser
if (window.DOMParser) {
var parser = new DOMParser();
var kml = parser.parseFromString(data, "text/xml");
} else { // Internet Explorer
var kml = new ActiveXObject("Microsoft.XMLDOM");
kml.loadXML(data);
}
//get the coordinates of Mozambique
var latLngs = kml.getElementsByTagName("coordinates")[0].childNodes[0].nodeValue.split(' ');
//create an array of LatLngs
var mzLatLngs = [];
for (var i = 0; i < latLngs.length; i++) {
var latLng = latLngs[i].split(',');
//<coordinates> for this FusionTable comes in lng,lat format
mzLatLngs.push(new google.maps.LatLng(latLng[1], latLng[0]));
}
//initialize the mozambique polygon
mozambique = new google.maps.Polygon({
paths: mzLatLngs,
fillColor: 'transparent',
strokeColor : 'transparent',
map: map
});
//make the mozambique polygon "transparent" for clicks (pass clicks to map)
google.maps.event.addListener(mozambique, 'click', function(event) {
google.maps.event.trigger(map, 'click', event);
});
});
}
Call the above initMozambique function in your second gmap().bind('init'... :
$('#map_canvas').gmap().bind('init', function(event, map) {
initMozambique(map);
...
Now you can check the mozambique-polygon for containsLocation after dragging
...
}).dragend(function(event) {
if (!google.maps.geometry.poly.containsLocation(event.latLng, mozambique)) {
alert('You are not allowed to drag the marker outside Mozambique');
}
//I need to check if the marker is over the FusionTablesLayer and block the drag.
//var test = google.maps.geometry.poly.containsLocation(event.latLng, world_geometry);
}).click(function() {
})
...
See forked fiddle, working demo with the code above -> http://jsfiddle.net/yb5t6cw6/
Tested in Chrome, FF and IE, ubuntu and windows.
I have noticed some different behavior with the following APIS
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false"></script>
http://jsfiddle.net/x8dSP/2062/
Sometimes the polygon layer renders ontop of the balloon layer, and sometimes the opposite.
It seems like after the map is "cached?" in the browser it will render with the polygon layer ontop. Is there anyway to prevent this? Or to have one layer always be in the background? Unfortunately I cannot map these layers in one kml.
The layers get rendered in the order they are received from the server (which is not necessarily the order in which they appear in the code). You can force one to load after the other by waiting for the KmlLayer status_changed event before setting the map property of the second.
function initialize() {
var chicago = new google.maps.LatLng(-122.365662,37.826988);
var mapOptions = {
zoom: 11,
center: chicago
}
var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
var ctaLayer = new google.maps.KmlLayer({
url: 'https://sites.google.com/site/gmaptest123/kml/nst.kml'
});
google.maps.event.addListener(ctaLayer, "status_changed", function() {
ctaLayer2.setMap(map);
});
ctaLayer.setMap(map);
var ctaLayer2 = new google.maps.KmlLayer({
url: 'https://sites.google.com/site/gmaptest123/kml/HelloKml6.kml'
});
}
google.maps.event.addDomListener(window, 'load', initialize);
updated fiddle
I've got it working here
Add these two parameters to your markers layer:
pane: "floatPane",
preserveViewport: true
So it looks like this:
var ctaLayer2 = new google.maps.KmlLayer({
url: 'https://sites.google.com/site/gmaptest123/kml/HelloKml6.kml',
pane: "floatPane",
preserveViewport: true
});
The default is, I believe, mapPane, which has a lower z-index than floatPane.
There is an interesting method in this link. Here is the actual ordering code:
// BEGIN SEQUENTIAL KML LOADING CODE
// This ensures the layers are drawn in order: cone, warnings, track
// Draw coneLayer
coneLayer.setMap(map);
// Listen for when coneLayer is drawn
var listener1 = google.maps.event.addListener(coneLayer, 'metadata_changed', function() {
// When it's drawn (metadata changed), clear listener, draw warningsLayer ...
google.maps.event.clearListeners(coneLayer, 'metadata_changed');
warningsLayer.setMap(map);
// .. and listen for when warningsLayer is drawn
var listener2 = google.maps.event.addListener(warningsLayer, 'metadata_changed', function() {
// When it's drawn, clear listener, draw trackLayer ...
google.maps.event.clearListeners(warningsLayer, 'metadata_changed');
trackLayer.setMap(map);
// ... and listen for when trackLayer is drawn
var listener3 = google.maps.event.addListener(trackLayer, 'metadata_changed', function() {
// When it's drawn, clear listener and blank out the map-loading sign
google.maps.event.clearListeners(trackLayer, 'metadata_changed');
$('#loadingIndicator').html("");
});
});
});
// END SEQUENTIAL KML LOADING CODE