Coordinate Reflection issue with Leaflet - javascript

Salutations all and happy holidays.
I Noticed an interesting behavioral quirk while trying to draw polygon layers with L.geoJson(). consider the following code:
var polygonCoords = [
{"type": "Feature",
"properties": {"group": "Violations"},
"geometry": {
"type" : "Polygon",
"coordinates": [[
[-107.69348, 43.22519],
[-105.48523, 42.99259],
[-107.7594, 42.26105]
]]
}
}];
and
var polygons = L.polygon([
[43.22519, -107.69348],
[42.99259, -105.48523],
[42.26105, -107.7594]
]);
Now, both work in their respective contexts. I was just wondering why the coordinate matrix within L.polygon() has to be reflected in order to show up where one expects it to be when passed into L.goeJson() like so:
var jsonPoly = L.geoJson(polygonCoords, {
style: function(feature) {
if (feature.properties.group == "Violations") {
return {color: "#ff0000"};
}
}
});
Or is this an oversight within leaflet? Also, is there a way to automate this reflection with say toGeoJson(polygons)?
Thanks so much all.

When creating a geoJson layer the coordinates are expected to match the GeoJSON standard (x,y,z or lng, lat, altitude) (GeoJSON position specs)
If you have string of GeoJSON where your coordinates are not in this format, you can create your GeoJSON layer with a custom coordsToLatLng function that will handle this conversion to the standard's format (Leaflet Doc)
If you have a polygon layer and want to add it to an existing GeoJSON feature group you can do something like:
var polygons = L.polygon([
[43.22519, -107.69348],
[42.99259, -105.48523],
[42.26105, -107.7594]
]);
var gg = polygons.toGeoJSON();
var jsonFeatureGroup = L.geoJson().addTo(map);
jsonFeatureGroup.addData(gg);
map.fitBounds(jsonFeatureGroup.getBounds());

Related

map.fitBounds to dynamically refit map view to display visibile features in MapBox

My map contains multiple features, the ids for all these features are stored in an array: featureIds.
My application contains a button which toggles the visibililty of some of the features.
I am working on a JavaScript function reCenter() to follow this toggling. This function "zooms" out and refits the map view in accordance to the bounds of features which are now visible.
function reCenter() {
// new array for visible features
var visibleFeatures = [];
// retrieve the features which are visible and put them into the new array
for (var i = 0; i < featureIds.length; i++) {
if (map.getLayoutProperty(featureIds[i], "visibility") == "visible") {
visibleFeatures.push(map.queryRenderedFeatures(featureIds[i]));
}
}
// new array to store coordinates
coordinates = [];
// push coordinates for each visible feature to coordinates array
for (var j = 0; j < visibleFeatures.length; j++) {
coordinates.push(coord.geometry.coordinates);
}
// do fit as shown here : https://docs.mapbox.com/mapbox-gl-js/example/zoomto-linestring/
var bounds = coordinates.reduce(function (bounds, coord) {
return bounds.extend(coord);
}, new mapboxgl.LngLatBounds(coordinates[0], coordinates[0]));
map.fitBounds(bounds, {
padding: 20
});
}
Despite implementing the above and following the guidance provided at https://docs.mapbox.com/mapbox-gl-js/example/zoomto-linestring/. I receive the following error: TypeError: this._sw is undefined
How can one best dyanmically retrieve all coordinates of visibile features and pass them into map.fitBounds()?
Get all your features and create a FeatureCollection and with bbox function from Turf.js get the bounds of the FeatureCollection. here is how I do that:
note: use toWgs84 if your coordinates are not wgs84.
const features = [
{
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [
[your_feature_s_coordinates],
[...],
]
}
},
{another feature}
]
const FeatureCollection = {
type: "FeatureCollection",
features: features
};
map.fitBounds(bbox(toWgs84(FeatureCollection)));
Try Turf.js: js is an open-source JavaScript library used for spatial analysis.
And it provide's a method bbox (http://turfjs.org/docs/#bbox)
It take's some set of feature's and will automatically calculate bbox for you. and set that final bbox to fitbounds.
and there's a similar question asked earlier:
Mapbox GL JS getBounds()/fitBounds()

Polygon GeoJSON Features Will Load in Console But Will Not Display in Leaflet

GIS data and python are old hat to me but I am very new to web development and geospatial web applications.
I have followed a tutorial and a class that I am taking to get to the below script but I cannot get the resulting geojson object (the polygon layer) to display within leaflet. I can however, log all of the features of the polygon layer to the console. Furthermore, within the console I can clearly see the correct type, properties, and coordinate arrays of the geojson object. I can also clearly see all of the features within the leaflet map object within the console.
Any input would be greatly appreciated. If needed I will be happy to post the getData.php code. I just don't think that is the problem.
var map,
fieldsin = ["campus_nam", "status", "schnumber", "type"],
autocomplete = [];
$(document).ready(initialize);
function initialize(){
map = L.map("mapdiv", {
center: [36.10, -80.25],
zoom: 12
});
var backgroundLayer = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(map);
//adding postgresql layers to map with getData.php
getData(fieldsin);
};
function getData(fieldsin){
$.ajax({
url: "php/getData.php",
data: { table: "public.school_campus", fields: fieldsin },
success: function(data){
mapData(data);
}
})
};
function mapData(data){
//remove existing map layers
map.eachLayer(function(layer){
//if not the tile layer
if (typeof layer._url === "undefined"){
map.removeLayer(layer);
}
})
//create geojson container object
var geojson = {
"type": "FeatureCollection",
"features": []
};
//split data into features
var dataArray = data.split(", ;");
//pop off the last value of the array because it is an empty string.
dataArray.pop();
//build geojson features
dataArray.forEach(function(d){
d = d.split(", "); //split the comma seperated data string up into individual attribute values
var test = d[fieldsin.length].concat("}");
//feature object container
var feature = {
"type": "Feature",
"properties": {}, //properties object container
//"geometry": JSON.parse(d[fieldsin.length]) //parse geometry
"geometry": JSON.parse(d[fieldsin.length]) //parse geometry
};
//bulding properties for properties container above
for (var i=0; i<fieldsin.length; i++){
feature.properties[fieldsin[i]] = d[i];
};
//add feature names to autocomplete list
if ($.inArray(feature.properties.campus_nam, autocomplete) == -1){
autocomplete.push(feature.properties.campus_nam);
};
//console.log(feature.geometry)
geojson.features.push(feature);
//var campusLayer = L.geoJSON(geojson).addTo(map);
var campusLayer = L.geoJSON(geojson, {
style: {
fillColor: "#CC9900",
color: "#66ffff",
weight: 1
},
onEachFeature: function (feature, layer) {
var html = "";
for (prop in feature.properties){
html += prop+": "+feature.properties[prop]+"<br>";
};
layer.bindPopup(html);
}
}).addTo(map);
});
};
Adding a sample of your resulting GeoJSON object would have surely helped in understanding your situation.
However I highly suspect that you have simply inverted the coordinates:
Leaflet expects [latitude, longitude] order
GeoJSON expects [longitude, latitude] order
See also https://macwright.org/lonlat/
Therefore there is a very high chance your Leaflet GeoJSON Layer Group is actually added onto your map, but you would have to zoom out to see your features on a completely different place and distorded.
Looks like you also have not specified the appropriate CRS to your Leaflet map instance, or you need to convert the coordinates from your backend to the Leaflet's default EPSG:3857.
Note that the GeoJSON spec requests WGS84 CRS, which is the same input for EPSG:3857.

D3 adds path for geojson but not topojson

I'm trying to display a map using D3. I have the same map as a geoJSON file and a topoJSON file. When I load in the geoJSON file, it renders on the page and the <path> tag gets filled with d=.... But when I change the url to get the topoJSON file, the <path> tag remains empty, even though I'm leaving the rest of the code untouched. The svg still renders with the topoJSON request, but nothing appears in it. Any thoughts on what might be going on?
My code:
var width = 550;
var height = 570;
var arizonaProjection = d3.geoMercator()
.center([-111.6602, 34.2744])
.scale(4500)
.translate([width/2, height/2]);
var path = d3.geoPath()
.projection(arizonaProjection);
var svg = d3.select("#map").append("svg")
.attr("height", height)
.attr("width", width);
d3.json("geojson/Arizona.geojson", function(error, Arizona) {
svg.append("path")
.attr("d", path(Arizona));
console.log(path);
});
The file geojson/Arizona.geojson is stored in a different directory of my local server, as is the topojson file at topojson/Arizona.json.
Sample of the topoJSON:
"transform":{
"scale":[0.00003998538143372804,0.000031941344085415994],
"translate":[-114.81659,31.33218]
},
"objects":{
"Arizona_88_to_89":{
"type":"GeometryCollection",
"geometries":[
{
"arcs":[[0,1,2,3,4,5]],
"type":"Polygon",
"properties":{
"startcong":"88",
"district":"1",
"statename":"Arizona",
"member":{
"88":{"7845":{"party":"Republican","name":"Rhodes, John Jacob","district":"1"}},
"89":{"7845":{"party":"Republican","name":"Rhodes, John Jacob","district":"1"}}
},
"endcong":"89",
"id":"004088089001"
}
},{
"arcs":[[6,-5,7,-3,8,-1,9,10,11,12]],
"type":"Polygon",
"properties":{
"startcong":"88",
"district":"2",
"statename":"Arizona",
"member":{
"88":{"10566":{"party":"Democrat","name":"Udall, Morris K.","district":"2"}},
"89":{"10566":{"party":"Democrat","name":"Udall, Morris K.","district":"2"}}
},
"endcong":"89",
"id":"004088089002"
}
},{
"arcs":[[-12,13,-10,-6,-7,14]],
"type":"Polygon",
"properties":{
"startcong":"88",
"district":"3",
"statename":"Arizona",
"member":{
"88":{"10623":{"party":"Democrat","name":"Senner, Georg F., Jr.","district":"3"}},
"89":{"10623":{"party":"Democrat","name":"Senner, Georg F., Jr.","district":"3"}}
},
"endcong":"89",
"id":"004088089003"
}
}
]
}
Sample of the geoJSON:
{"type": "FeatureCollection", "features": [{"geometry": {"type": "MultiPolygon", "coordinates": [[[[-112.75515, 33.99991], [-112.75073, 33.99984], [-112.75034, 33.99992], [-112.74655, 33.99991], [-112.74509, 33.9999], [-112.7442, 33.9999], [-112.74395, 33.9999], [-112.74346, 33.99977], [-112.74331, 33.99973], [-112.74262, 33.99955],
A d3 geopath takes a geojson object, not a topology/topojson object. To use topojson with a d3 geoPath, you must first convert it back to geojson. You can do this quite easily with topojson.js:
var featureCollection = topojson.feature(Arizona, Arizona.objects.counties)
Of course you can get the features as an array with:
var features = topojson.feature(Arizona, Arizona.objects.counties).features
The above assumes that topology.objects contains a property for counties, you'll have to take a look at your topojson to find out if counties is correct or not (I'm guessing you might be showing counties). If you used a tool such as mapshaper.org, the property name may be the same as the original file name.

Export leaflet map to geojson

Is it possible to export geojson from leaflet to save the map state?
I want to store the markers, zoom & map center to load it later.
There is plenty of ways to load geojson on leaflet, but I can't figure out any option to export the map to geojson...
There's no "out-of-the-box" option to export all the markers on the map to GeoJSON but it's something you can do easily do yourself. Leaflet's L.Marker has a toGeoJSON method:
Returns a GeoJSON representation of the marker (GeoJSON Point Feature).
http://leafletjs.com/reference.html#marker-togeojson
For example:
// Create a marker
var marker = new L.Marker([0, 0]);
// Get the GeoJSON object
var geojson = marker.toGeoJSON();
// Log to console
console.log(geojson);
Will output to your console:
{
"type":"Feature",
"properties":{},
"geometry":{
"type":"Point",
"coordinates":[0,0]
}
}
If you want to store all the markers added to your map in a GeoJSON collection, you could do something like this:
// Adding some markers to the map
var markerA = new L.Marker([0, 0]).addTo(map),
markerB = new L.Marker([1, 1]).addTo(map),
markerC = new L.Marker([2, 2]).addTo(map),
markerD = new L.Marker([3, 3]).addTo(map);
// Create an empty GeoJSON collection
var collection = {
"type": "FeatureCollection",
"features": []
};
// Iterate the layers of the map
map.eachLayer(function (layer) {
// Check if layer is a marker
if (layer instanceof L.Marker) {
// Create GeoJSON object from marker
var geojson = layer.toGeoJSON();
// Push GeoJSON object to collection
collection.features.push(geojson);
}
});
// Log GeoJSON collection to console
console.log(collection);
Will output to your console:
{
"type":"FeatureCollection",
"features":[{
"type":"Feature",
"properties":{},
"geometry":{
"type":"Point",
"coordinates":[0,0]
}
},{
"type":"Feature",
"properties":{},
"geometry":{
"type":"Point",
"coordinates":[1,1]
}
},{
"type":"Feature",
"properties":{},
"geometry":{
"type":"Point",
"coordinates":[2,2]
}
},{
"type":"Feature",
"properties":{},
"geometry":{
"type":"Point",
"coordinates":[3,3]
}
}]
}
Edit: However, as the QP found out, if you're able to put your markers into a L.LayerGroup, L.FeatureGroup or L.GeoJSON layer you can just use it's toGeoJSON method which returns a GeoJSON featurecollection:
Returns a GeoJSON representation of the layer group (GeoJSON FeatureCollection).
http://leafletjs.com/reference.html#layergroup-togeojson
If you want to store the map's current bounds (center and zoom) you could simply add it to the collection doing this:
var bounds = map.getBounds();
var collection = {
"type": "FeatureCollection",
"bbox": [[
bounds.getSouthWest().lng,
bounds.getSouthWest().lat
], [
bounds.getNorthEast().lng,
bounds.getNorthEast().lat
]],
"features": []
};
You can later restore it by using the bbox member in conjunction with L.Map's setBounds method. That's it. You could send it to the server or download it via dataurl whatever you like. Hope that helps, good luck.
I've found a simplier solution based on iH8's answer and a colleague's help.
First, create a FeatureGroup layer and add it to the map:
var drawnItems = new L.FeatureGroup();
map.addLayer(drawnItems);
Then add the marker (or other elements) to the layer:
var marker = new L.marker([lat, lon]).addTo(drawnItems);
And export everything when you want to :
var collection = drawnItems.toGeoJSON();
var bounds = map.getBounds();
collection.bbox = [[
bounds.getSouthWest().lng,
bounds.getSouthWest().lat,
bounds.getNorthEast().lng,
bounds.getNorthEast().lat
]];
// Do what you want with this:
console.log(collection);

Calculating the percent overlap of two polygons in JavaScript

Here's the problem: I have geoJSON and topoJSON files that give me the polygons for Census block groups and voting precincts. I'm trying to see by how much a given Census block group overlaps with a given precinct.
I've seen a couple examples of what I'm after in other languages—i.e. R and in some GIS tools—but I'm trying to write this as a Node.js script. A few questions:
Is there an NPM module (I've done a fair amount of Googling, but I haven't found one) that can spit out the percent overlap?
Is there an algorithm, or an exmaple written in another language, that I should know about (I've looked, but I haven't the foggiest where to begin) and that I could port to JavaScript?
Failing these, can someone explain to me how I would go about thinking about creating an algorithm for this?
In the end, the final product would look something like this—imagining that I have arrays of precincts and blockgroups, and each one is an object with a geometry property that contains the polygon data for the precinct or block group, and also imagining that I have a function called overlap that, when passed two polygons spits out the percent overlap:
// Iterate over each precinct.
_.each( precincts, function ( precinct ) {
// Iterate over each blockgroup.
_.each( blockgroups, function ( blockgroup ) {
// Get the overlap for the current precinct and blockgroup.
var o = overlap( precinct.geometry, blockgroup.geometry );
// If they overlap at all...
if ( o > 0 ) {
// ...Add information about the overlap to the precinct.
precinct.overlaps.push({
blockgroup: blockgroup.id,
overlap: o
});
}
}
}
(I've seen this module, but that only gives if the polygons overlap, not by how much they do.)
turf-intersect takes two polygons and returns a polygon representing the intersection.
geojson-area takes a polygon and returns the area in square meters.
npm install turf
npm install geojson-area
var turf = require('turf');
var geojsonArea = require('geojson-area');
var poly1 = {
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[
[-122.801742, 45.48565],
[-122.801742, 45.60491],
[-122.584762, 45.60491],
[-122.584762, 45.48565],
[-122.801742, 45.48565]
]]
}
}
var poly2 = {
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[
[-122.520217, 45.535693],
[-122.64038, 45.553967],
[-122.720031, 45.526554],
[-122.669906, 45.507309],
[-122.723464, 45.446643],
[-122.532577, 45.408574],
[-122.487258, 45.477466],
[-122.520217, 45.535693]
]]
}
}
var intersection = turf.intersect(poly1, poly2);
var area_intersection = geojsonArea.geometry(intersection.geometry);
var area_poly1 = geojsonArea.geometry(poly1.geometry);
var percent_poly1_covered_by_poly2 = (area_intersection / area_poly1)*100;
To compute the overlapping percentage
Compute the intersection of the two polygons
Intersection = intersect(Precinct, Block)
Divide the area of Intersection by the area of the parent polygon of interest.
Overlap = area(Intersection) / area(Parent)
It is a little unclear what you mean by the percent overlap. The parent polygon could be one of several possibilities
a) area(Intersection) / area(Precinct)
b) area(Intersection) / area(Block)
c) area(Intersection) / area(Precinct union Block)
As for a javascript library, this one seems to have what you need Intersection.js
There's also the JSTS Topology Suite which can do geospatial processing in JavaScript. See Node.js examples here.
You can do spatial overlaps with Turf js. It has the features of JSTS (and more), but is very modular.

Categories

Resources