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.
Related
This question is a follow-up from my previous post .
After successfully creating a World Bubble Map based on geolocation on Observable Notebook, with help from Chris.
I want to make my Notebook user-friendly. For instance, I have already some sets of data that are plotted as bubbles according to parameter selection. Now what I want to achieve is, if somebody wants to use their own set of data on my Observable Notebook I want their data to be plotted as bubbles based on geolocation of the countries (same as my data).(Make a path where they can upload their own data) And since the dropdown menu content has been manually modified according to the parameters name, will it be possible to make the dropdown menu content change automatically when someone adds a new set of data ?
How can I do something like this ? Any suggestions? Thank you in advance.
A link to my Notebook : Bubble Maps
This is partial solution with some open Issues :
I have created a second map where users can use their own set of data to plot the bubbles. Although, it will only work if the Country Code of uploaded .CSV data is the same as ISO_A2 (Country Code)from the .JSON file used in my Notebook. There are some issues I could not resolve. Mainly 1 . Some countries appear on the top left corner of the map which is not actually correct (the second map should actually plot the countries like the first map as both map has same longitude and latitude) 2. The same zoom function is working for the first map but not for the second map (that is kind of confusing me) 3. Will it be possible to generate input buttons automatically from the uploaded file, or should it be done manually every time. If I could get some help and suggestions, it would be so great. I have included a dummy data in my notebook (in the data paneāFile Attachments) which can be used to test the second map. Here is the link : Bubble Map
The Radio button code:
viewof userparameter = Inputs.select(new Map([ ["Area (Sq. Km)", "areasqkm"],["Population", "population"], ["Airports", "airports"],["Gold in tonnes","Goldtonnes"],["GDP in dollar","gdpdollar"] ]), { value: "Area (Sq. Km)", label: "User Parameter to show:" })
The zoom properties:
viewof userparameter = Inputs.select(new Map([ ["Area (Sq. Km)", "areasqkm"],["Population", "population"], ["Airports", "airports"],["Gold in tonnes","Goldtonnes"],["GDP in dollar","gdpdollar"] ]), { value: "Area (Sq. Km)", label: "User Parameter to show:" })
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.
we are using OpenLayers 4.6.4 with the ol-ext extension in order to show beautiful looking clusters. That is working fine so far, but we need to link each cluster with custom informations (like putting a simple field into the cluster object) and once the cluster is clicked on the map, i want to retrieve it with the arguments delivered in the callback.
I was not able to find a simple example on putting custom fields on a cluster and retrieving them once i click them on the map.
The event i add my listener is handled by ol.interaction.SelectCluster from ol-ext
Any ideas?
You cannot change the cluster features so easily, but that's actually not necessary to get information about it.
Cluster is just a layer source which clusters (who would have guessed) an underlaying source. It creates Features, which in turn have the represented Features stored in a property features.
The ol-ext example demonstrates how to read the contained features:
var selectCluster = new ol.interaction.SelectCluster(.....);
selectCluster.getFeatures().on(['add'], function (e)
{ var c = e.element.get('features');
if (c.length==1)
{ var feature = c[0];
$(".infos").html("One feature selected...<br/>(id="+feature.get('id')+")");
}
else
{ $(".infos").text("Cluster ("+c.length+" features)");
}
})
Without any interaction, you could do this:
map.on('singleclick', function(evt) {
const feature = map.forEachFeatureAtPixel(
evt.pixel,
function(someFeature){ return someFeature; }, // stop at the very first feature
);
const containedFeatures = feature.get('features');
});
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.