I have two geoJSON feature collections that I need to add to the map, and I also want them to be toggled on and off via the layer visibility controllers as shown in http://leafletjs.com/examples/layers-control.html
how can I do this?
There is also a very good tutorial on the usage of L.GeoJSON, Leaflet's GeoJSON layer, which can be found here: http://leafletjs.com/examples/geojson.html and here is the reference for L.GeoJSON: http://leafletjs.com/reference.html#geojson You already found the tutorial on L.control.layers, here is the reference for it: http://leafletjs.com/reference.html#control-layers
It's actually quite simple to do, it's just a matter of creating a layercontrol, loading a GeoJSON file into your script by using your favorite XHR library, use the retrieved data to defined a L.GeoJSON layer and add it to the layercontrol. In code:
// Create the layercontrol and add it to the map
var controlLayers = L.control.layers().addTo(map);
// Loading a GeoJSON file (using jQuery's $.getJSON)
$.getJSON('/my-folder/my-file.json', function (data) {
// Use the data to create a GeoJSON layer and add it to the map
var geojsonLayer = L.geoJson(data).addTo(map);
// Add the geojson layer to the layercontrol
controlLayers.addOverlay(geojsonLayer, 'My GeoJSON layer title');
});
A working example on Plunker: http://plnkr.co/edit/tFVrrq?p=preview
Since you create a layer when loading a GeoJSON, you can add it to the layer control as an Overlay Layer (simply modify that example and replace the cities layer.
Related
Background:
I'm currently integrating HERE maps into our web-based application. I'm trying both - HERE provided Javascript API and Leaflet at the same time to find the best approach for our use-case.
While JavaScript API provided by HERE maps is OK, rendering wise Leaflet performs much better when using raster tiles.
Issue:
It would be fine by me to use raster tiles + leaflet, but our application also needs to display traffic incidents data.
Traffic incident data is provided by HERE in JSON and XML formats (Documentation link, Example JSON). They provide [Z]/[X]/[Y], quadkey, prox, bbox, or corridor filters which can be used to retrieve filtered data set.
I've tried using [Z]/[X]/[Y] addressing with custom L.TileLayer implementation which loads appropriate JSON, converts it to GeoJSON and displays GeoJSON on map. However that approach is very inefficient and significant performance drop is visible.
Question:
Maybe anyone has already solved this issue and could share any insights on how the HERE traffic incidents could be shown on Leaflet map without encountering performance issues?
I created the following script, which works without any performance issues:
var fg = L.featureGroup().addTo(map);
function loadTraffic(data) {
fg.clearLayers();
var d = data.TRAFFICITEMS.TRAFFICITEM.map((r) => {
var latlngs = [];
if (r.LOCATION.GEOLOC) {
if (r.LOCATION.GEOLOC.ORIGIN) {
latlngs.push(L.latLng(r.LOCATION.GEOLOC.ORIGIN.LATITUDE, r.LOCATION.GEOLOC.ORIGIN.LONGITUDE));
}
if (r.LOCATION.GEOLOC.TO) {
if (L.Util.isArray(r.LOCATION.GEOLOC.TO)) {
r.LOCATION.GEOLOC.TO.forEach((latlng) => {
latlngs.push(L.latLng(latlng.LATITUDE, latlng.LONGITUDE));
})
} else {
latlngs.push(L.latLng(r.LOCATION.GEOLOC.TO.LATITUDE, r.LOCATION.GEOLOC.TO.LONGITUDE));
}
}
}
var desc = r.TRAFFICITEMDESCRIPTION.find(x => x.TYPE === "short_desc").content;
return {
latlngs,
desc
}
})
console.log(d);
d.forEach((road)=>{
L.polyline(road.latlngs,{color: 'red'}).addTo(fg).bindPopup(road.desc);
});
map.fitBounds(fg.getBounds())
}
If this script is not working for you, please share your json file.
Ok, so I've found a solution for this task. Apparently I was on a good path, I only needed to optimize my implementation.
What I had to do to achieve appropriate performance is:
Create custom CircleMarker extension which would draw custom icon on canvas
Create JS worker which would fetch the data from a given URL, transform it to GeoJSON and return GeoJSON to it's listener
Create custom GridLayer implementation, which, in fetchTile function, creates worker instance, passes it a link with appropriate [Z]/[X]/[Y] coordinates already set, adds listener, which listens for worker's done event and returns empty tile
On worker's done event, custom GridLayer implementation creates GeoJSON layer, adds it to the dictionary with coordinates as a key and, if zoom level is still the same - adds that layer to the map
Add zoomend observer on a map, which removes any layers that does not match current zoom level from the map
Now the map is definitely usable and works way faster than original HERE JS API.
P.S. Sorry, but I can't share the implementation itself due to our company policies.
There are many beautiful charts on Geographical Maps that can be drawn with AmCharts JS library as in https://www.amcharts.com/demos/#javascript-maps
However, I was wondering if it is possible to create a Custom Map. For example, I want to create a Global map with all Color-coded countries, but want to show US and Canada as one individual Country without any intermediate boundary between them. All other Countries should remain same.
Really appreciate for any pointer on above direction.
Thanks,
With amCharts v4, we have switched to using GeoJSON for our maps.
While I'm sure there's some kind of way to merge geographical polygons using mapshaper, I haven't tried it out myself yet and have been getting comfortable using the free software, QGIS.
There's already a tutorial out there on merging polygons if you're interested. I'll give a quick, specific rundown here anyway.
Download & install QGIS, then go ahead and grab the latest version of amCharts v4 worldLow.json (or worldHigh.json if you want more detail), in this case I've used worldLow.json:
https://github.com/amcharts/amcharts4-geodata/blob/master/dist/script/json/worldLow.json
Raw file:
https://raw.githubusercontent.com/amcharts/amcharts4-geodata/master/dist/script/json/worldLow.json
Make a copy of the .json file or otherwise rename it, it will be overwritten in the steps below.
Open QGIS and start a new project
Menu: Layer -> Add Vector Layer -> Source(s) -> Vector Dataset(s): Choose above file -> Add
Enable editing on the layer by either: Clicking pencil icon, Menu: Layer -> Toggle Editing, or in the bottom left Layers panel right click the layer and select Toggle Editing in the context menu
Menu: Edit -> Select -> Select feature(s)
Select the US and Canada polygons.
Menu: Edit -> Merge selected features (near the bottom)
Save your project, save layer edits, now your json file will be updated.
It should look something like this:
https://gist.github.com/notacouch/485b8525a360c15690f1ab23cbf04940
Now to refer to this in your map, you can do so via:
// Create map polygon series
var polygonSeries = chart.series.push(new am4maps.MapPolygonSeries());
polygonSeries.geodataSource.url = "https://gist.githubusercontent.com/notacouch/485b8525a360c15690f1ab23cbf04940/raw/e6742c279571ae02166027817351a4250b1bea69/worldLow--canadia.json";
In addition, you can generate a new color per country by using the default ColorSet, e.g.
// Have amCharts generate a new color for each mapPolygon once they're available
//
// Learn more about Event Listeners:
// {#link https://www.amcharts.com/docs/v4/concepts/event-listeners/}
polygonSeries.events.on("datavalidated", function() {
polygonSeries.mapPolygons.each(function(polygon, index) {
// Learn more about ColorSets:
// {#link https://www.amcharts.com/docs/v4/concepts/colors/#Color_sets}
polygon.fill = chart.colors.getIndex(index);
});
});
// Create hover state and set alternative fill color
//
// Learn more about States:
// {#link https://www.amcharts.com/docs/v4/concepts/states/}
var hs = polygonTemplate.states.create("hover");
// Darken the polygon's current color
//
// Learn more about Adapters:
// {#link https://www.amcharts.com/docs/v4/concepts/adapters/}
hs.adapter.add("fill", function(fill) {
return fill.brighten(-0.2);
});
Here's a demo with all that thrown together:
https://codepen.io/team/amcharts/pen/abd95bebcfb65a74d9472043d63351fc
That's probably undesirable, you can also color via data binding, or via a heat map.
For customizing maps in v4 in general, I recommend checking out our guide on Creating custom maps. Between mapshaper, QGIS, and all the map-related files out there, there's bound to be a way to get the customization you want and possibly in short order, too.
Hope this helps.
I had more than 600 kml files to load in a single google map.
Initially I tried with KmlLayer(), but it didn't work due to the amount of kml files, so I found GeoXML3, and it works really fine.
Now I need to retrieve the path's coords for every polygon created with GeoXML3. Here I found the method getPaths() that seems to be just what I'm looking for, but it doesn't work because now I don't create polygons using the class Polygon but using the class geoxml3
for (i=0; i < controlli.length; i++)
{
appo = kmlurl + controlli[i].id + ".kml";
appo = appo.replace(" ", '_');
area[controlli[i].id] = new geoXML3.parser({
map: map,
zoom: false,
});
area[controlli[i].id].parse(appo);
//here I would like to do something like: 'area[controlli[i].id].getPaths()'
}
How can I do this?
The google.maps.Polygon objects created by geoXml3 to represent the KML polygons can be accessed 2 ways:
area[controlli[0].id].docs[0].placemarks[0].polygon.getPath()
working jsfiddle
area[controlli[0].id].docs[0].gpolygons[0].getPath()
working jsfiddle
where geoXml is a reference to the parser object (your area[controlli[i].id])
and i is a sequential reference to the placemarks (or the polygons) in the KML).
If you are using it on a KML file loaded asynchronously, you need to wait for the parsed event, or use the data in the afterParse function.
I am using a simple Mapbox layer control calling MB data layers (below).
I need to add a few more marker layers to this, but not sure how to get a mapbox ID. How can I accomplish this?
L.mapbox.accessToken = 'pk.eyJ1IjoibWFwc3RlciIsImEiOiI3RmFfME5ZIn0.73sdzUFNqSsGQzjlsnimaA';
var map = L.map('map').setView([38.8922,-77.0348], 14);
var layers = document.getElementById('menu-ui');
addLayer(L.mapbox.tileLayer('examples.map-i87786ca'), 'Base Map', 1);
addLayer(L.mapbox.tileLayer('examples.bike-lanes'), 'Bike Lanes', 2);
addLayer(L.mapbox.tileLayer('examples.bike-locations'), 'Bike Stations', 3);
function addLayer(layer, name, zIndex) {
layer
.setZIndex(zIndex)
.addTo(map);
code is from Mapbox toggling layers template
At the moment you're using their example ID and maps. You're not supposed to do that. If you would have read at the bottom of the page you posted it says:
Use this example by copying its source into your own HTML page and replacing the Map ID with one of your own from your projects.
Where "your projects" is linked to https://www.mapbox.com/projects/. When you're not logged in you get a nice dialog which asks you to login or register. Once you've done that you'll get your very own ID and you are able to create projects. When creating a project you'll get a Map ID per project. It's all pretty selfexplanatory.
EDIT: If you want to insert a separate layer with features, you've got to create a project with only a markerlayer. Save it and copy the id. You can include that in another map by using L.mapbox.featureLayer:
var mapId = 'examples.map-zr0njcqy'; // use your feature mapid
var features = L.mapbox.featureLayer(mapId); // declare featureLayer
features.on('ready', function () { // Wait untill features are loaded
addLayer(features); // add it the same your tilelayers
}
You can also use this to load external geojson files by just using an URL instead of a mapid.
See the example: https://www.mapbox.com/mapbox.js/example/v1.0.0/features-from-another-map/
And the reference: https://www.mapbox.com/mapbox.js/api/v2.1.5/l-mapbox-featurelayer/
I am doing a choropleth map using leaflet and JSON data. I got a clean tutorial in leaflet site. In that tutorial the used us-states.js JSON file where the choropleth was based on the feature.properties.density feature of the JSON if I am not wrong. If we see the JS file we can found there is a field called "ID" which is state id. I have a database where I have 5 different categories of data based on state ID. Categories are population, density, male_population, female_population, literacy_rate. I am calling those data through ajax and get a GeoJSON data state-wise as follows (Its a dummy data).
[{"state_id":"01","population":"123456","density":"1234","male_pop":"65432","female_pop":"57421","literacy_rate":"98"}]
...
[{"state_id":"50","population":"123456","density":"1234","male_pop":"65432","female_pop":"57421","literacy_rate":"98"}]
I want to integrate this data as choropleth value. when I call on literacy function the variation of color will be based on literacy_rate data. I can make the changes in getcolor function category wise.
Thanks
I would recommend using Leaflet Data Visualization Framework (DVF) plugin, located here. This plugin contains a set of Layers, (such as a Choropleth layer) and a set of functions to help color-code your data.
The example located here will demonstrate the DVF Choropleth Data layer.
Using DVF, you can create a color function to create your scaling color values. For example, this function will generate a function from yellow to red. The point values contain the range of values, and the HSL Hue.
var yellowToRed = new L.HSLHueFunction(new L.Point(50, 60), new L.Point(100, 0));
DVF contains a L.ChoroplethDataLayer layer that will accept your GeoJSON, and your color function, and will generate the layer for you.
var layer = new L.ChoroplethDataLayer(geoJSON, {
// For the full options, see the documentation
displayOptions: {
// The display will be colored by your 'density' property in your GeoJSON. This accesses the feature object directory, so the 'properties' prefix is required if you're going to access a GeoJSON property on your data.
'properties.density': {
// A legend will automatically be generated for you. You can add this as a control. This displayName property will be the title for this layer's legend.
displayName: 'Density',
color: yellowToRed
}
};
});
This is just a simple example. For your data, it might look slightly different. Go through the DVF documentation and samples and you'll find what's right for you.