GeoJson and D3.js multipolygon - javascript

I have this geometric shape file, so no map of a city.
I store it in a GIS database as GeoJson. Now, I want to visualize the geojson data. I created the GeoJson data first with QGIS and exported it as Coordinate Reference System WGS 84 EPSG:4326. This is an example data of Shapefile one:
{
"type":"FeatureCollection",
"crs":{
"type":"name",
"properties":{
"name":"urn:ogc:def:crs:OGC:1.3:CRS84"
}
},
"features":[
{
"type":"Feature",
"properties":{
"Membership":0.000000,
"Membership_1":0.000000,
"Membership_2":0.000000,
"Membership_3":0.000000,
"Membership_4":0.000000,
"Membership_5":0.000000,
"Membership_6":0.000000,
"Membership_7":0.000000,
"Membership_8":0.000000,
"Membership_9":0.997638,
"Asymmetry":0.622090,
"Elliptic_F":0.368607,
"Density":1.720265,
"Radius_of_":2.122269,
"Rectangula":0.701797,
"Radius_of__1":0.341230,
"Main_direc":63.913780,
"Mean_red":251.683422,
"Mean_green":253.246326,
"Mean_blue":251.654027,
"Shape_inde":1.663047,
"Compactnes":2.373016,
"Roundness":1.781040,
"Border_ind":1.603306
},
"geometry":{
"type":"MultiPolygon",
"coordinates":[
[
[
[
0.0,
293.0
],
[
116.0,
293.0
],
[
116.0,
288.0
],
[
117.0,
288.0
],
[
117.0,
287.0
],
GeoJson Shapefile two the geometry is at the end:
{
"type":"FeatureCollection",
"crs":{
"type":"name",
"properties":{
"name":"urn:ogc:def:crs:OGC:1.3:CRS84"
}
},
"features":[
{
"type":"Feature",
"properties":{
"Ratio_red":0.337287,
"Ratio_gree":0.324566,
"Ratio_blue":0.338147,
"Asymmetry":0.233023,
"Elliptic_F":0.835821,
"Density":2.111246,
"Radius_of_":1.191572,
"Max_diff":0.040743,
"Rectangula":0.958607,
"Ratio_DSM_":1.001866,
"Diff_DSM_w":0.604676,
"LengthWidt":1.266667,
"Radius_of__1":0.894812,
"Main_direc":0.507535,
"Standard_d":4.209384,
"Standard_d_1":13.755727,
"Standard_d_2":12.358206,
"Standard_d_3":16.194083,
"Standard_d_4":21.437695,
"Standard_d_5":0.486436,
"Mean_slope":195.593284,
"Mean_slope_1":34.988806,
"Mean_red":143.451493,
"Mean_green":138.041045,
"Mean_blue":143.817164,
"Mean_DSM":324.615672,
"Shape_inde":1.038440,
"Mean_Diff_":0.604676,
"Compactnes":1.063433,
"Brightness":141.769900,
"Roundness":0.296759,
"Area_m2":1.715200,
"Border_ind":1.000000
},
"geometry":{
"type":"MultiPolygon",
"coordinates":[
[
[
[
-1.796831198293312,
46.775409744271464
],
[
-1.796815938387422,
46.775411620389058
],
The geometry is at the end of the file. I already tried things from this post but this works only for polygons and not multipolygons:
Venue/Indoor Map using D3.js and Geojson
I tried to visualize both with the following code:
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
//Width and height
var w = 800;
var h = 800;
var colors = d3.scale.category20();
var projection = d3.geo.mercator()
.translate([w/2, h/2]);
var path = d3.geo.path()
.projection(projection);
//Define path generator
var path = d3.geo.path();
//Create SVG element
var svg = d3.select("body").append("svg").attr({width: w, height: h});
//Load in GeoJSON data
d3.json("imageOne.json", function(json) {
//Bind data and create one path per GeoJSON feature
svg.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.style("fill", function(d,i){return colors(i)});
});
</script>
After running the script I get for the first data the following result:
Trying the script for the second image I get a white page.
I uploaded the two shape files
Shapefiles

I struggled with this type of issue for days. Turns out the coordinate system used to serialize the map was projected instead of geometric, meaning that the data was already stored as x and y values on a 2d plane, not coordinates on a sphere.
Mike Bostock explains it very where in this google groups post:
https://groups.google.com/forum/#!topic/d3-js/OSp_sMZjfok
The issue is that d3.geo.projection is primarily intended for converting spherical coordinates to Cartesian coordinates, so when you create a d3.geo.projection instance from a raw projection function, it assumes spherical coordinates. Meaning, it assumes your raw projection function takes radians λ and φ as input, converts the input coordinates from degrees to radians, and performs adaptive resampling on the output.
All of which makes it great for implementing new geographic projections, but you’ll probably want to take a different route for implementing a custom Cartesian projection.
One approach is to implement a custom geometry stream. This is a lower-level API that lets you control exactly how the geometry is transformed, and is suitable for a simple scale and translate:
http://bl.ocks.org/mbostock/6216797
So armed with this knowledge, of course pumping the points thru a projection that expects the data to be spherical is going to result in a big mess.
If I viewed the shapefile or geojson in QGIS application, at the bottom right it shows the Coordinate Reference System (CRS) used to encode the values. In my case it was using 5320 (which is projected/2d) instead of something like 4326 (which is a geographic coordinate system)

Related

OpenStreetMap vanishes and popups are visible in wrong geolocations [duplicate]

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());

How do I find all markers/features within a given radius of any arbitrary point and store the lat/lng of said markers?

Given a point, say [-75.343, 39.984], how do I go about finding all features/markers within a 5km radius of it? I'm utilizing turf.js so I'll be using their circle() function to generate the circle about the point.
Would this work?
const center = [-75.343, 39.984];
const radius = 5;
const options = {steps: 10, units: 'kilometers', properties: {foo: 'bar'}};
const circle = turf.circle(center, radius, options);
}
const features = map.queryRenderedFeatures(
circle,
{ filter: {["within", circle] : true} }
);
I'm hoping to find all features within the circle and be able to store them in an array or in a database for further processing like accessing the feature's lat/lng, etc.
Thank you for the help!
Using queryRenderedFeatures you will be able to get the features that are actually in the viewport (visible). In case your source data is GeoJSON, you can use querySourceFeatures, so it will look to all your source features:
const filteredFeatures = map.querySourceFeatures('routes', {
filter: ['within', circle]
});

Using D3-Geo to plot a shape

I'm using d3-geo package to plot points and shapes in latitude and longitude space. I would like to plot a simple polygon, triangle, square, star, etc centered on a point location. I would also, if possible, like to plot text at a given [lat,lon].
I currently have the below code working and plotting a ring at the given coords [-40.5, 65.5]. I would, however, like to be able to define different shapes at this location, is there an easy way of doing this without manually defining the shape myself? There is an empty 'properties' field that I'm unable to find any documentation on that could be used? D3-geo documentation and google searches have yielded zilch so far.
let geoGenerator = geoPath()
.projection(projection)
.pointRadius(4)
.context(context); //2d Canvas contect
context.beginPath();
geoGenerator({
type: "Feature",
geometry: {
type: "Point",
coordinates: [-40.5, 65.5]
},
properties: {}
});
context.stroke();
Geojson does not have any property in its specification that specifies the type of symbol (symbol shape, color, size, etc) that should be drawn. Geojson only specifies the geometry (point, line, polygon, etc) of the drawn object in geographic coordinates.
Of course you can use the properties property of a geojson feature to hold symbol data, it just has no effect on the rendering the feature unless you build that functionality yourself.
While geojson doesn't have any specifications for symbology, the geoPath generator in D3 let's you specify one part of a drawn symbol: the radius of a point (as points are dimensionless otherwise). However, other than this, d3-geo doesn't offer any support for drawing specific symbols, it can only project geometry.
To draw a symbol at a specific geographic coordinate, you'll want project the coordinate (projection([longitude,latitude])). Now you have a coordinate in pixel values, you can use that coordinate to draw your symbol. You don't want to try and draw the symbol in geographic coordinates as this isn't scalable and it is dependent on projection.
Here's a simple implementation with d3-symbol (I haven't drawn the rest of the world, just two points, but they are projected properly):
var context = d3.select("canvas").node().getContext("2d");
var points = [[-136,63],[-123,50]];
var projection = d3.geoMercator();
var shape = d3.symbol()
.type(d3.symbolWye)
.context(context)
.size(200);
var shapey = function(lonlat) {
// Get xy Data
var xy = projection(lonlat);
// save without translation.
context.save();
// position symbol:
context.translate(...xy);
// Draw symbol:
context.beginPath();
shape();
context.fill();
// Remove translation:
context.restore();
}
points.forEach(shapey);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<canvas width="500" height="300"></canvas>
Of course you could specify your own shapes too, here's a simple square implementation:
var context = d3.select("canvas").node().getContext("2d");
var points = [[-136,63],[-123,50]];
var projection = d3.geoMercator();
var shape = function(xy) {
var offset = 10;
var x = xy[0];
var y = xy[1];
context.beginPath();
// draw a sqaure:
context.moveTo(x-offset,y-offset);
context.lineTo(x-offset,y+offset);
context.lineTo(x+offset,y+offset);
context.lineTo(x+offset,y-offset);
context.lineTo(x-offset,y-offset);
context.fill();
context.strokeStyle = "steelblue";
context.lineWidth = 5;
context.lineCap = "square"
context.stroke();
}
var shapey = function(lonlat) {
// Get xy Data
var xy = projection(lonlat);
// save without translation.
shape(xy);
}
points.forEach(shapey);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<canvas width="500" height="300"></canvas>
Of course you could get a lot fancier in these basic functions, but for the purposes of demonstration they should be sufficient.

How to display an image that can be changed at any time as a map using Mapbox?

In my app, user must have an ability to add a floor plan to the map. User uploads a PNG image using a simple form, and then this image must be displayed as a map background. So what we're having here is:
A PNG image that can be changed by the user at any time. I receive a URL of this image from the server.
Dimensions of the image (width and height).
Mapbox has sources and layers which I need to utilize to add this image as a background of the map, and the actual world map must not display at all.
I've seen a lot of examples like this (this one is using mapbox-gl-js):
...
"sources": {
"overlay": {
"type": "image",
"url": "https://www.mapbox.com/mapbox-gl-js/assets/radar.gif",
"coordinates": [
[-80.425, 46.437],
[-71.516, 46.437],
[-71.516, 37.936],
[-80.425, 37.936]
]
}
},
...
And this (this one is using deck.gl layers):
import DeckGL from '#deck.gl/react';
import {BitmapLayer} from '#deck.gl/layers';
const App = ({data, viewport}) => {
const layer = new BitmapLayer({
id: 'bitmap-layer',
bounds: [-122.5190, 37.7045, -122.355, 37.829],
image: 'https://raw.githubusercontent.com/uber-common/deck.gl-data/master/website/sf-districts.png'
});
return (<DeckGL {...viewport} layers={[layer]} />);
}
But they always have predefined coordinates for the image. Because my image can be updated by the user at any time, I need to somehow calculate these coordinates, taking into account aspect ratio of the image. I'm not any good at math, so can you please help me out? deck.gl has the ability to specify coordinate system of the layer and even 4x4 projection matrix, but I don't quite understand how I can use that for my case.
Alright, I solved the problem. The key to the solution was to stop trying to make the plan fill the entire map, but instead to resize the image to make it really small and place it at [0, 0] coordinates on the map. That way we can assume that the world is flat here and not worry about the curvature of it at all.
So when the map loads, I'm loading the image to get its dimensions:
this.map.current.on('load', () => {
const img = new Image()
const self = this
img.addEventListener('load', function () {
// ...
})
img.src = planUrl
})
Then when it's done, in the image's load handler I'm resizing the image and creating LngLatBounds of it. I'm just simply dividing width and height here to get lng and lat of the image — they will be less than 1 on both lng and lat, so I don't think the curvature of the earth will be a problem on this level:
img.addEventListener('load', function () {
const maxWidth = 1
const maxHeight = 0.5
const [width, height] = resizeImage(
this.naturalWidth,
this.naturalHeight,
maxWidth,
maxHeight
)
const sw = [-width / 2, -height / 2]
const ne = [width / 2, height / 2]
const bounds = new LngLatBounds(sw, ne)
// ...
})
Then I'm adding a source with the floor plan and a layer displaying the plan to the map:
self.map.current.addSource('plan', {
type: 'image',
url: planUrl,
coordinates: [
bounds.getNorthWest().toArray(),
bounds.getNorthEast().toArray(),
bounds.getSouthEast().toArray(),
bounds.getSouthWest().toArray()
]
})
self.map.current.addLayer({
id: 'image',
source: 'plan',
type: 'raster'
})
And then I'm setting map bounds equal to 2 times the size of the image's bounds, so the plan will have a nice padding around it.
const mapBounds = new LngLatBounds(sw, ne)
mapBounds.extend(new LngLatBounds([-width, -height], [width, height]))
self.map.current.setMaxBounds(mapBounds)
There are probably better solutions to it than this one but looks like it works just fine for me. Hopefully it'll help someone else.

Calculate area of polygon in d3

I cannot calculate the area of a polygon in D3 using the function path.area()
I have tried feeding it a list of coordinates as follows:
var d = [
[-1, 415.44],
[146.93, 304.47],
[195.45, 152.13],
[-1, 134.64]
]
path.area(d)
I have also tried to feed it (what I think is) a TopoJSON object, as follows:
path.area({
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": d},
});
The first attempt gives you value 0. The second gives you 'NaN'. Do you guys know what I am doing wrong? As an FYI, I need to calculate the area of a polygon that I reference as follows:
d3.select("#IDofpolygon");
You have to use d3.polygonArea, which:
Returns the signed area of the specified polygon. If the vertices of the polygon are in counterclockwise order (assuming a coordinate system where the origin ⟨0,0⟩ is in the top-left corner), the returned area is positive; otherwise it is negative, or zero.
Here is the demo:
var d = [
[-1, 415.44],
[146.93, 304.47],
[195.45, 152.13],
[-1, 134.64]
];
var area = d3.polygonArea(d);
console.log(area)
<script src="https://d3js.org/d3.v4.min.js"></script>

Categories