The Google Maps JavaScript version 3 API library documentation clearly explains:
The Google Maps API supports the KML
and GeoRSS data formats for displaying
geographic information. These data
formats are displayed on a map using a
KmlLayer object, whose constructor
takes the URL of a publicly accessible
KML or GeoRSS file.
There are even several Stack Overflow questions about how to load local data:
Loading a local .kml file using google maps?
Google Maps kml files
Some of the answers have pointed to third party libraries which can parse KML locally without the file needing to be public:
geoxml3
GeoXML
EGeoXml
And while these solutions are good if you have a need to keep your data private, I simply want to make development easier. When running locally I obviously cannot parse my KML and therefore lose functionality that I am trying to test. I've posted a single generic KML file on a publicly available site, but then have to have different development code to render one thing vs. something else when running for real.
What are my options for local development to render what would be publicly available dynamically generated KML files?
It seems like you've outlined the options pretty well:
If you want to work with local data, without involving a publicly accessible webserver, you'll need to use a javascript-based approach to parse the KML and load it onto the map. While this won't perfectly replicate the Google functionality, it is likely good enough for initial development if you only care about displaying the KML features. In this case, I'd probably set up a stub class, like this:
// I'll assume you have a global namespace called MyProject
MyProject.LOCAL_KML = true;
MyProject.KmlLayer = function(url) {
// parse the KML, maybe caching an array of markers or polygons,
// using one of the libraries you list in your question
};
// now stub out the methods you care about, based on
// http://code.google.com/apis/maps/documentation/javascript/reference.html#KmlLayer
MyProject.KmlLayer.prototype.setMap = function(map) {
// add the markers and polygons to the map, or remove them if !map
}
// etc
Now either put a switch in the code, or comment/uncomment, or use a build script to switch, or whatever your current process is for switching between dev and production code:
var kmlPath = "/my.kml";
var kmlLayer = MyProject.LOCAL_KML ?
new MyProject.KmlLayer(MyProject.LOCAL_KML_HOST + kmlPath) :
new google.maps.KmlLayer(MyProject.PRODUCTION_KML_HOST + kmlPath);
kmlLayer.setMap(myMap);
If, on the other hand, you need all of the functionality in the Google KmlLayer, or you want to make sure things work with the production setup, or you don't want to bother stubbing out the functionality Google provides, then you'll need to upload it to a publicly available server, so that Google can do its server-side processing.
Aside from the obvious options (FTP, a command-line script to upload your new KML file, etc), most of which require you to do something manually before you load your map page, you might consider building the update into the page you're loading. Depending on the platform you're using, this might be easier to do on the back-end or the front-end; the key would be to have a script on your public server that would allow the KML to be updated:
Get KML string from request.POST
Validate the KML string (just so you aren't opening your server to attacks)
Write to a single file, e.g. "my.kml"
Then, when you view your map page, update the remote KML based on the data from localhost. Here's a client-side version, using jQuery:
// again, you'd probably have a way to kill this block in production
if (MyProject.UPDATE_KML_FROM_LOCALHOST) {
// get localhost KML
$.get(MyProject.LOCAL_KML_HOST + kmlPath, function(data) {
// now post it to the remote server
$.post(
MyProject.DEV_KML_HOST + '/update_kml.php',
{ kml: data },
function() {
// after the post completes, get the KML layer from Google
var kmlLayer new google.maps.KmlLayer(MyProject.DEV_KML_HOST + kmlPath);
kmlLayer.setMap(myMap);
}
);
})
}
Admittedly, there are a lot of round-trips here (page -> localhost, page -> remote server, Google -> remote server, Google -> page), so this is going to be slow. But it would allow you to have Google's code properly render dynamic KML data produced on localhost, without having to take a separate manual step every time you reload the page.
Definitely, Google Maps KmlLayer is designed for you to send your data to them.
https://developers.google.com/maps/documentation/javascript/kml
Have a look the following log.
//console
var src = 'https://developers.google.com/maps/documentation/javascript/examples/kml/westcampus.kml';
var kmlLayer = new google.maps.KmlLayer(src, {
suppressInfoWindows: true,
preserveViewport: false,
map: your_gmap_object
});
Creating Marker, Polygon, they are all browser side parsing and rendering.
As you can see from next network log, KmlLayer class send source URL to Google Server to parse it and (do something in their end) and send the parsed result back to your browser to render.
//REQUEST from browser
https://maps.googleapis.com/maps/api/js/KmlOverlayService.GetOverlays?1shttps%3A%2F%2Fdevelopers.google.com%2Fmaps%2Fdocumentation%2Fjavascript%2Fexamples%2Fkml%2Fwestcampus.kml&callback=_xdc_._lidt3k&key=AIzaSyBeLTP20qMgxsQFz1mwLlzNuhrS5xD_a_U&token=103685
//RESPONSE from google server
/**/_xdc_._lidt3k && _xdc_._lidt3k( [0,"kml:cXOw0bjKUSmlnTN2l67v0Sai6WfXhSSWuyNaDD0mAzh6xfi2fYnBo78Y2Eg","|ks:;dc:tg;ts:51385071|kv:3|api:3",...
["KmlFile"],[[37.423017,-122.0927],[37.424194,-122.091498]],[["g74cf1503d602f2e5"],["g58e8cf8fd6da8d29"],["ge39d22e72437b02e"]],1,[["client","2"]],-21505,[["ks",";dc:tg;ts:51385071"],["kv","3"],["api","3"]]] )
As #capdragon mentioned above, it would be better parse KML by yourself.
UPDATE
Here is compact KML parser code.
This only for google.maps Marker and Polygon.
html
<input type='file' accept=".kml,.kmz" onchange="fileChanged()">
script, I used typescript but it is pretty same with javascript
file: any
fileChanged(e) {
this.file = e.target.files[0]
this.parseDocument(this.file)
}
parseDocument(file) {
let fileReader = new FileReader()
fileReader.onload = async (e: any) => {
let result = await this.extractGoogleCoords(e.target.result)
//CREATE MARKER OR POLYGON WITH result here
console.log(result)
}
fileReader.readAsText(file)
}
async extractGoogleCoords(plainText) {
let parser = new DOMParser()
let xmlDoc = parser.parseFromString(plainText, "text/xml")
let googlePolygons = []
let googleMarkers = []
if (xmlDoc.documentElement.nodeName == "kml") {
for (const item of xmlDoc.getElementsByTagName('Placemark') as any) {
let placeMarkName = item.getElementsByTagName('name')[0].childNodes[0].nodeValue.trim()
let polygons = item.getElementsByTagName('Polygon')
let markers = item.getElementsByTagName('Point')
/** POLYGONS PARSE **/
for (const polygon of polygons) {
let coords = polygon.getElementsByTagName('coordinates')[0].childNodes[0].nodeValue.trim()
let points = coords.split(" ")
let googlePolygonsPaths = []
for (const point of points) {
let coord = point.split(",")
googlePolygonsPaths.push({ lat: +coord[1], lng: +coord[0] })
}
googlePolygons.push(googlePolygonsPaths)
}
/** MARKER PARSE **/
for (const marker of markers) {
var coords = marker.getElementsByTagName('coordinates')[0].childNodes[0].nodeValue.trim()
let coord = coords.split(",")
googleMarkers.push({ lat: +coord[1], lng: +coord[0] })
}
}
} else {
throw "error while parsing"
}
return { markers: googleMarkers, polygons: googlePolygons }
}
output
markers: Array(3)
0: {lat: 37.42390182131783, lng: -122.0914977709329}
...
polygons: Array(1)
0: Array(88)
0: {lat: -37.79825999283025, lng: 144.9165994157198}
...
Related
I am using Google Apps Script to create a page, on which I would like to embed maps. The maps themselves would be static, but the map could be different depending on other parameters (it’s a genealogy page, and I’d like to display a map of birth and death locations, and maybe some other map points, based on a selected individual).
Using Google’s Maps service, I know that I can create a map, with a couple points built in.
Function getMapImage() {
var map = Maps.newStaticMap()
.setSize(600,400)
.addMarker('Chicago, Illinois') // markers would be based on a passed parm; this is just test data
.addMarker('Pocatello, Idaho');
// *** This is where I am looking for some guidance
return(); // obviously, I'm not returning a blank for real
}
Within the map class, there are a number of things I can do with it at this point.
I could create a URL, and pass that back. That appears to require an API account, which at this point, I do not have (and ideally, would like to avoid, but maybe I’ll have to do that). It also appears that I will run into CORB issues with that, which I think is beyond my knowledge (so if that’s the solution, I’ll be back for more guidance).
I could create a blob as an image, and pass that back to my page. I have tried this using a few different examples I have found while researching this.
Server Side
function getMapImage() {
var map = Maps.newStaticMap()
.setSize(600,400)
.addMarker('Chicago, Illinois')
.addMarker('Pocatello, Idaho');
var mapImage = map.getAs("image/png");
// OR
// var mapImage = map.getBlob();
return(mapImage);
}
Page side
<div id=”mapDiv”></div>
<script>
$(function() {
google.script.run.withSuccessHandler(displayMap).getMapImage();
}
function displayMap(mapImage) {
var binaryData = [];
binaryData.push(mapImage);
var mapURL = window.URL.createObjectURL(new Blob(binaryData, {type: "image/png"}))
var mapIMG = "<img src=\'" + mapURL + "\'>"
$('#mapDiv').html(mapIMG);
}
</script>
The page calls getMapImage() on the server, and the return data is sent as a parm to displayMap().
var mapIMG ends up resolving to <img src='blob:https://n-a4slffdg23u3pai7jxk7xfeg4t7dfweecjbruoa-0lu-script.googleusercontent.com/51b3d383-0eef-41c1-9a50-3397cbe83e0d'> This version doesn't create any errors in the console, which other options I tried did. But on the page, I'm just getting the standard 16x16 image not found icon.
I’ve tried a few other things based on what I’ve come across in researching this, but don’t want to litter this post with all sorts of different code snippets. I’ve tried a lot of things, but clearly not the right thing yet.
What’s the best / correct (dare I ask, simplest) way to build a map with Google’s Map class, and then serve it to a web page?
EDIT: I added a little more detail on how the server and page interact, in response to Tanaike's question.
Modification points:
I think that in your script, Blob is returned from Google Apps Script to Javascript using google.script.run. Unfortunately, in the current stage, Blob data cannot be directly sent from from Google Apps Script to Javascript. I think that this might be the reason of your issue.
In this case, I would like to propose to directly create the data URL at the Google Apps Script side. When your script is modified, it becomes as follows.
Modified script:
Google Apps Script side:
function getMapImage() {
var map = Maps.newStaticMap()
.setSize(600, 400)
.addMarker('Chicago, Illinois')
.addMarker('Pocatello, Idaho');
var blob = map.getAs("image/png"); // or map.getBlob()
var dataUrl = `data:image/png;base64,${Utilities.base64Encode(blob.getBytes())}`;
return dataUrl;
}
Javascript side:
$(function() {
google.script.run.withSuccessHandler(displayMap).getMapImage();
});
function displayMap(mapURL) {
var mapIMG = "<img src=\'" + mapURL + "\'>"
$('#mapDiv').html(mapIMG);
}
In your Javascript side, $(function() {google.script.run.withSuccessHandler(displayMap).getMapImage();} is not enclosed by ). Please be careful this.
Note:
In my environment, when I saw <div id=”mapDiv”></div>, this double quote ” couldn't be used. So if in your environment, an error occurs by <div id=”mapDiv”></div>, please modify ” to " like <div id="mapDiv"></div>.
Reference:
base64Encode(data)
According to the documentation I can pre-define the set of markers and other objects while creating the map, which then will be displayed. I do not wish to put all the possible markers/images/rectangles/etc in the JS code. I've read that people calculate the visible area on every tiles moving/zooming, do a HTTP request and server returns needed markers.
However, I would like to use another way, as it is a little more efficient:
1. For example, currently Leaflet automatically asks for the tiles 0:0 and 0:1;
2. In addition it could make a HTTP request and ask the server: "Hey, give me also the markers for the tiles 0:0 and 0:1".
3. Completely remove markers which are on the tiles which have become invisible.
Are the steps 2-3 possible and how if Yes?
What you're asking looks pretty similar to how Leaflet.VectorGrid works: each "tile" request is a request for vector data, not for a raster image. In fact, perhaps using protobuffer vector tiles is the right approach for your scenario.
VectorGrid relies on the logic implemented by L.GridLayer to handle the tile loading/unloading logic.
If you insist on doing this yourself, I suggest reading the Leaflet tutorials on creating plugins first; have a look to the source code for Leaflet's L.GridLayer and for VectorGrid to see how those work, and then something like:
L.GridLayer.MarkerLoader = L.GridLayer.extend({
initialize: funcion(url, options) {
this._url = url;
this._markerStore = {};
L.GridLayer.prototype.initialize.call(this, options);
},
createTile: function(coords, done) {
var key = this._tileCoordsToKey(coords);
var data = {
s: this._getSubdomain(coords),
x: coords.x,
y: coords.y,
z: coords.z
};
var tileUrl = L.Util.template(this._url, L.extend(data, this.options));
fetch(tileUrl).then(function(response){
// Parse the response, with either response.json()
// or response.text() or response.blob().
// See https://developer.mozilla.org/en-US/docs/Web/API/Response
// Create a bunch of markers based on the parsed response
// The specific syntax depends on the format of the data structure
var markers = data.map(function(point){
return L.marker(point);
});
// Add those markers to a L.LayerGroup, add that LayerGroup
// to a dictionary (to remove it later), and finally add it to the map
var group = L.layerGroup(markers);
this._markerStore[key] = group;
// Mark the tile as ready
done();
});
// Return an empty <div> as a tile. Real data will be loaded async and
// put in LayerGroups anyway.
return L.DomUtil.createElement('div');
},
_removeTile: function(key) {
// Whenever a tile is pruned, remove the corresponding LayerGroup
// from the map and from the _markerStore dictionary
this._markerStore[key].remove();
delete this._markerStore[key];
return L.GridLayer.prototype._removeTile.call(this, key);
}
});
I am trying to scan a folder for all .shp files and add them to my leaflet map as a layer. The problem I'm having is that AJAX doesn't appear to be able to scan a folder, rather it is only capable of searching for specific files. I need it to be able to scan the folder because the .shp files will be overwritten periodically with new file names and I don't know how many files will be there at a given time. If there are no files in the folder, I need a popup to notify that maps are unavailable (this works in the current website). The code for this section is provided below. I've tried using PHP, but I can't figure out how to incorporate that with JavaScript.
I'm not a programmer, but I have stumbled through developing our website on my own. You can view it here: http://hsvfms.azurewebsites.net/map.html
If I haven't provided enough information or have given this information out of context, please let me know. Also, check out the website and let me know what you think.
var noMaps = L.control ();
var profile01 = new L.LayerGroup();
var profile02 = new L.LayerGroup();
$.ajax({
type:"GET",
url:"Aldridge_Unet/",
success: function(data) {
$(data).find("a:contains(.shp)").each(function(){
var mapList=[];
var profile0 = new L.Shapefile(mapList[0],{color:'DarkCyan',fillOpacity:'0.5', opacity:'5', weight:'1'}).addTo(profile01);
var profile1 = new L.Shapefile(mapList[1],{color:'DarkCyan',fillOpacity:'0.5', opacity:'5', weight:'1'}).addTo(profile02);
})
},
error: function (xhr, status, error) {
if(xhr.status==404){
noMaps = L.control ({position:'bottomleft'});
noMaps.onAdd = function(map) { var div = L.DomUtil.create('div', 'info legend');
div.innerHTML += '<img src="Images/map_not_available.png" alt="legend" style="width:275px;height:75px;background-color:white">';
return div;};
}
}
});
var overlays = [{groupName:"Inundation Boundaries", expanded:false, layers:{"Max Value":profile01}},];
Cannot be done. JavaScript code cannot access the filesystem of the computer the browser is running in (let alone watch a path for changes).
AJAX (and fetch and similar techniques) are just ways of fetching information from another computer (the web server). If your question really is "when something changes in the web server, how can I update the clients?" then the answer is probably WebSockets, socket.io, and similar techniques.
I am trying to set up a system that loads a KMZ file and displays it using google maps and then tests if the user has clicked within the bounds of a polygon created by the KMZ. I have searched all through these forums and the web and haven't found a working solution.
I am having no problem loading and reading the KMZ file and the polygon is displaying perfectly on the map. But when I try to use the polygon data returned by geoXML3 to test if a location is within the bounds then I get a variety of errors depending on how I approach it.
I am loading the geoxml3.js files locally and the parser for the KMZ files, and as I say that works fine so I won't include all that.
The KMZ file is local to the server and reads fine.
This is what I have:
<script>
var zone;
var polzone;
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 11,
center: {lat: 0, lng: 0}
});
zone = new geoXML3.parser({map: map});
zone.parse('test.kmz');
}
function testlimit(){
polzone = zone.docs[0].gpolygons[0];
console.log( google.maps.geometry.poly.containsLocation(new google.maps.LatLng("0", "0"), polzone));
}
</script>
As you can see I'm testing just with hard coded long and lattitude values and it's giving me the error "Uncaught TypeError: b.get is not a function".
The testlimit function is fired when a button is clicked.
I initially had more code but as it wasn't working, this is where I've ended up. Any help would be appreciated.
Alright, never mind then, Apparently the gpolygon variable is an array.
So it should be
polzone = zone.docs[0].gpolygons[0];
One of those days I should have stayed in bed I guess...
I try to get traffic data at a spesific time and place using yandex maps. I look this page( api of yandex maps ). And I can show traffic data on my own map. Using geocoding, I extract some data(place names, coordinates etc) from yandex maps. But I don't know how I extract only traffic data. How can I do it? Is there any function on yandex map api?
My simple code that shows traffic data on map is below
ymaps.ready(init);
var myMap,
myPlacemark;
function init(){
myMap = new ymaps.Map ("mapId", {
center: [28.968484, 41.01771],
zoom: 7
});
myPlacemark = new ymaps.Placemark([28.968484, 41.01771], {
content: 'Moscow!',
balloonContent: 'Capital of Russia'
});
// This page should show traffic and the "search on map" tool
ymaps.load(['package.traffic', 'package.search'], addControls);
myMap.geoObjects.add(myPlacemark);
}
function addControls(map) {
myMap.controls.add('trafficControl').add('searchControl');
var trafficControl = new ymaps.control.TrafficControl({shown: true});
map.controls.add(trafficControl);
function updateProvider () {
trafficControl.getProvider('traffic#actual').update();
}
// We will send a request to update data every 4 minutes.
window.setInterval(updateProvider, 1 * 60 * 1000);
}
There is no legal way to do it. There are some notes at forums, where people use internal api of Yandex maps, but they as far as I know rapidly banned by Yandex team. Yandex does not provide you any traffic data separatly from maps. I recommened you to take a look at Google Maps Api, there you could get traffic info on route your have specified. Example:
final String baseUrl = "http://maps.googleapis.com/maps/api/directions/json";// path to Geocoding API via http
final Map<String, String> params = new HashMap<String, String>();
params.put("sensor", "false");// if data for geocoding comes from device with sensor
params.put("language", "en");
params.put("mode", "driving");// move mode, could be driving, walking, bicycling
params.put("origin", "Russia, Moscow, Poklonnaya str., 12");// start point of route as address or text value of lon and lat
params.put("destination", "Russia, Moscow, metro station Park Pobedy");// the same for end point
final String url = baseUrl + '?' + encodeParams(params);// generate final url
final JSONObject response = JsonReader.read(url);// perform request and read answer where we could also get coordinates
//results[0]/geometry/location/lng and results[0]/geometry/location/lat
JSONObject location = response.getJSONArray("routes").getJSONObject(0);
location = location.getJSONArray("legs").getJSONObject(0);
final String distance = location.getJSONObject("distance").getString("text"); // get distance for route
final String duration = location.getJSONObject("duration").getString("text"); // get duration for route
For more info just take a look at https://developers.google.com/maps/