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.
Related
When making a level in kaboomJS with a large tile map collisions, things start to get slow... So I was wondering if there was an easy way to merge multiple tiles like maybe a whole row of blocks could be treated as one large block?
1. Tiles don't have to fit in the grid
If you want to reduce the number of Game Objects in the Scene at a time you can have a single symbol in your level definition represent a Game Object that spans multiple grid tiles. So if you want a lot of platforms that are 3 grid squares wide, you don't need 3 objects per platform, you can just use a single character to represent a 3x1 rect:
import kaboom from "kaboom"
// initialize context
kaboom()
// load assets
loadSprite("bean", "sprites/bean.png")
addLevel(
// Note: the hyphens here hare just place holders to remind us that the
// game objects created by ➁ and ➂ are actually taking up 2 and 3 grid
// squares respectively.
[
" ⚥ ",
" ",
" ➂-- ",
" ",
" ➁- ",
" ",
" ",
"################################",
],
{
width: 32,
height: 32,
"⚥": () => [
sprite("bean"),
area(),
body(),
"player"
],
"#": () => [
rect(32, 32),
outline(2),
area(),
solid(),
],
"➁": () => [
rect(32 * 2, 32),
outline(2),
area(),
solid(),
],
"➂": () => [
rect(32 * 3, 32),
outline(2),
area(),
solid(),
],
}
);
const player = get("player")[0];
player.onUpdate(() => {
const left = isKeyDown('left') || isKeyDown('a');
const right = isKeyDown('right') || isKeyDown('d');
if (left && !right) {
player.move(-500, 0);
} else if (right && !left) {
player.move(500, 0);
}
camPos(player.pos);
});
onKeyPress("space", () => {
if (player.isGrounded()) {
player.jump();
}
});
Obviously if you had many different shapes and sizes this would be quite onerous.
2. Advanced: Quad Trees and Loading/Unloading Regions
I actually ran into this problem myself on a recent Kaboom project and decided to completely overhaul the built in addLevel() with my own implementation that loaded from a bitmap instead of a bunch of strings, and then organized the level data into a quadtree so that I could quickly find chunks that overlapped the visible area, and load and unload game objects based on their visibility. The technique and code are a bit to complex it include here, so I'll just link to the Repl and the relevant source code: level-loader.ts and lib-quad-tree.ts and the usage in level-one.js .
My geoJSON file: coordinates key contains normal array with coordinates, but then contains an array filled with mini-arrays of more coordinates. I cannot loop through the file to extract the center of the route in order to add my google feature layer to my google api call.
When I try to break up apart the mini arrays and add it to the overall total array, the route becomes visible, but is not correct. The route goes over the water and through buildings, it does not know the sequence. Any help would be great. The bold breaks the pattern of the whole feature.
ex:
{"type":"Feature","properties":{"OBJECTID":3,"LENGTH":3919.4410000000003,"NAME":"Anacostia Riverwalk-SW","STATUS":"Open","MAINTENANC":"DDOT","Shape_Length":1194.6479669066844,"MILES":0.7423183712121213},"geometry":{"type":"MultiLineString","coordinates":[[[-77.02215895389972,38.87673054256091],[-77.02186869854079,38.87653957420972],[-77.02150193815677,38.87588299990856],[-77.0208280524279,38.874621556490816],[-77.02075697415529,38.87448850380858],[-77.02062323938105,38.874211704408985],[-77.02008845385471,38.87338800912766],[-77.01937789804083,38.87202111748801],[-77.01768265826789,38.87204526498413]],
[[-77.02594024042205,38.87974434948391],[-77.02509229875858,38.87909983220277],[-77.0237707432032,38.8780375415248],[-77.02215132050688,38.87674248295079]]
]}},
What you call mini-arrays is just the specification of geojson MultiLineStrings.
http://geojson.org/geojson-spec.html#multilinestring
For type "MultiLineString", the "coordinates" member is an array of LineString coordinate arrays.
So each array is describing a part of a line and together they form the resulting route. You can iterate over them by using 2 for loops (its an array of arrays)
var geojson = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "MultiLineString","coordinates":[
[[-77.02215895389972,38.87673054256091],[-77.02186869854079,38.87653957420972],[-77.02150193815677,38.87588299990856],[-77.0208280524279,38.874621556490816],[-77.02075697415529,38.87448850380858],[-77.02062323938105,38.874211704408985],[-77.02008845385471,38.87338800912766],[-77.01937789804083,38.87202111748801],[-77.01768265826789,38.87204526498413]],
[[-77.02594024042205,38.87974434948391],[-77.02509229875858,38.87909983220277],[-77.0237707432032,38.8780375415248],[-77.02215132050688,38.87674248295079]]
]
}
}
]
}
let coords = geojson.features[0].geometry.coordinates;
for (let i = 0; i < coords.length; i += 1) {
for (let j = 0; j < coords[i].length; j += 1) {
console.log(coords[i][j]);
}
}
I have a JSON array of 7,000 objects, which are all under one parent. I want to add two levels in front of them, a year level, then a month level.
Currently my structure looks like:
{
//apod is single representation of main parent
"apod" : {
//below is a unique identifier with nested data
"-KgpW6owlA2YGwKFj2V-" : {
"date" : "1995-06-16",
"explanation" : "If the Earth could somehow be transformed to the ultra-high density of a neutron star , it might appear as it does in the above computer generated figure. Due to the very strong gravitational field, the neutron star distorts light from the background sky greatly. If you look closely, two images of the constellation Orion are visible. The gravity of this particular neutron star is so great that no part of the neutron star is blocked from view - light is pulled around by gravity even from the back of the neutron star.",
"gsHdUrl" : "https://www.foo.com",
"gsThumbnailUrl" : "https://www.foo.com",
"hdurl" : "https://apod.nasa.gov/apod/image/e_lens.gif",
"media_type" : "image",
"service_version" : "v1",
"title" : "Neutron Star Earth",
"url" : "https://apod.nasa.gov/apod/image/e_lens.gif"
},
//below is another unique identifier with nested data
"-KgpW7fV9laX8YL30glD" : {
"date" : "1995-06-20",
"explanation" : "Today's Picture: June 20, 1995 The Pleiades Star Cluster Picture Credit: Mount Wilson Observatory Explanation: The Pleiades star cluster, M45, is one of the brightest star clusters visible in the northern hemisphere. It consists of many bright, hot stars that were all formed at the same time within a large cloud of interstellar dust and gas. The blue haze that accompanies them is due to very fine dust which still remains and preferentially reflects the blue light from the stars. We keep an archive of previous Astronomy Pictures of the Day. Astronomy Picture of the Day is brought to you by Robert Nemiroff and Jerry Bonnell . Original material on this page is copyrighted to Robert J. Nemiroff and Jerry T. Bonnell.",
"gsHdUrl" : "https://www.foo.com",
"gsThumbnailUrl" : "https://www.foo.com",
"hdurl" : "https://apod.nasa.gov/apod/image/pleiades2.gif",
"media_type" : "image",
"service_version" : "v1",
"title" : "Pleiades Star Cluster",
"url" : "https://apod.nasa.gov/apod/image/pleiades2.gif"
}
}
What I want to transform it to would look like:
{
"apod": {
"1995": {
"06": {
"-KgpW6owlA2YGwKFj2V-": {
"date": "1995-06-16",
"explanation": "If the Earth could somehow be transformed to the ultra-high density of a neutron star , it might appear as it does in the above computer generated figure. Due to the very strong gravitational field, the neutron star distorts light from the background sky greatly. If you look closely, two images of the constellation Orion are visible. The gravity of this particular neutron star is so great that no part of the neutron star is blocked from view - light is pulled around by gravity even from the back of the neutron star.",
"gsHdUrl": "https://www.foo.com",
"gsThumbnailUrl": "https://www.foo.com",
"hdurl": "https://apod.nasa.gov/apod/image/e_lens.gif",
"media_type": "image",
"service_version": "v1",
"title": "Neutron Star Earth",
"url": "https://apod.nasa.gov/apod/image/e_lens.gif"
},
"-KgpW7fV9laX8YL30glD": {
"date": "1995-06-20",
"explanation": "Today's Picture: June 20, 1995 The Pleiades Star Cluster Picture Credit: Mount Wilson Observatory Explanation: The Pleiades star cluster, M45, is one of the brightest star clusters visible in the northern hemisphere. It consists of many bright, hot stars that were all formed at the same time within a large cloud of interstellar dust and gas. The blue haze that accompanies them is due to very fine dust which still remains and preferentially reflects the blue light from the stars. We keep an archive of previous Astronomy Pictures of the Day. Astronomy Picture of the Day is brought to you by Robert Nemiroff and Jerry Bonnell . Original material on this page is copyrighted to Robert J. Nemiroff and Jerry T. Bonnell.",
"gsHdUrl": "https://www.foo.com",
"gsThumbnailUrl": "https://www.foo.com",
"hdurl": "https://apod.nasa.gov/apod/image/pleiades2.gif",
"media_type": "image",
"service_version": "v1",
"title": "Pleiades Star Cluster",
"url": "https://apod.nasa.gov/apod/image/pleiades2.gif"
}
},
"07": {}
},
"1996": {}
}
}
I want to keep the key->object mapping. What would be the best way to loop through this and restructure it? I'm somewhat lost as I haven't worked with editing/restructuring JSON that much.
Thank you
What you have are objects, not arrays. Anyway, I commented things out.
function convert( json ){
// `data` is a JavaScript object we get from parsing
// json. For simplicity we remove the `apod` property
// and add it prior to returning.
let data;
try {
data = JSON.parse(json).apod;
}
catch(e){
alert("invalid json!");
}
// Create a new object to store the mapped objects.
// We will convert this to a json string in the end.
const result = {};
// Iterate all properties.
const keys = Object.keys(data);
for( let key of keys ){
// Parse month and year.
const [year, month] = data[key].date.split("-", 2);
// If there hadn't been an occurrence of this year,
// introduce an object.
if( !result[year] )
result[year] = {};
// If there hadn't been an occurrence of this month
// in this year, introduce an object.
if( !result[year][month] )
result[year][month] = {};
// Add element to current year and month.
result[year][month][key] = data[key];
}
// Convert and return the mapped object as json.
return JSON.stringify({ apod: result });
}
{
"2015": {
"01": [{...}, {...}, ...],
"02": [{...}, ...],
...
"12": [...]
},
"2016": {
...
}
}
To loop via PHP you can:
foreach ($data as $year => $monthes) {
foreach ($monthes as $month => $objects) {
foreach ($objects as $object) {
// do something with $year, $month and $object
}
}
}
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());
I've a 3d model of a tube geometry. There are 18000 co-ordinates on production side. I am taking every 9th co-ordinate so that actually plotting 9000 co-ordinates to build a tube geometry. I've to use CanvasRenderer only.
Now when I use vertexColors: THREE.VertexColors in WebGLRenderer, the model displays different color on each face. When I change it to CanvasRenderer, the model turns into white color only. Even I change vertexColors: THREE.FaceColors, the result is same.
Please find below the link of jsfiddle and link of my previous where mrdoob added support for material.vertexColors = THREE.FaceColors to CanvasRenderer.
support for vertex color in canvas rendering
tube in canvas rendering
Please find below the image to apply colors based on values.
As shown in the image there are 12 values at 12 different degrees for every co-ordinate. So I've created a tube with radius segment of 12. Then I've stored these values into JSON file but as there 18000 points, the file becomes to heavy. Even though I am plotting 2000 points it takes too much time. For 2000 segments and each segment has 12 faces, there are 24000 faces on a tube.
Please find below the programming logic to apply color based on value of a parameter.
// get res values & apply color
var lblSeg=0; var pntId; var d=0; var faceLength=tube.faces.length;
var degrees = [ '30', '60', '90', '120', '150', '180', '210', '240', '270', '300', '330' ];
var faces = tube.faces; var degreeCntr=0; var degreeProp;
//console.log(faces);
var res30=0,res60=0,res90=0,res120=0,res150=0,res180=0,res210=0,res240=0,res270=0,res300=0,res330=0;
var res; var resDegree; var pnt=0;
// fetching json data of resistivity values at different degree as //shown in the image
var result = getResValue();
for(var k=0; k<faceLength; k++){
resDegree = degrees[degreeCntr];
degreeProp = "r"+resDegree;
res = result.resistivity[pnt][degreeProp];
objects.push(result.resistivity[pnt]);
f = faces[k];
color = new THREE.Color( 0xffffff );
if(res<5){
color.setRGB( 197/255, 217/255, 241/255);
}
else if(res>=5 && res<50){
color.setRGB( 141/255, 180/255, 226/255);
}
else if(res>=50 && res<100){
color.setRGB( 83/255, 141/255, 213/255);
}
else if(res>=100 && res<200){
color.setRGB( 22, 54, 92);
}
else if(res>=200 && res<300){
color.setRGB( 15/255,36/255,62/255);
}
else if(res>=300 && res<400){
color.setRGB( 220/255, 230/255, 241/255);
}
else if(res>=400 && res<700){
color.setRGB( 184/255, 204/255, 228/255);
}
else if(res>=700 && res<1200){
color.setRGB( 149/255, 179/255, 215/255);
}
else if(res>=1200 && res<1500){
color.setRGB( 54/255, 96/255, 146/255);
}
else if(res>=1700 && res<1800){
color.setRGB( 36/255, 84/255, 98/255);
}
else if(res>1900){
color.setRGB( 128/255, 128/255, 128/255);
}
for(var j=0;j<4;j++)
{
tube.vertices.push(f.centroid);
vertexIndex = f[ faceIndices[ j ] ];
p = tube.vertices[ vertexIndex ];
f.vertexColors[ j ] = color;
}
degreeCntr++;
if(degreeCntr==10){
degreeCntr=0;
}
if(k%12==0 && k!=0){
pnt++;
}
}
This logic takes too much time to render the model and the model becomes too heavy and we can't perform other operations. The FPS on android drops at 2-3 FPS. Actually I've to render this model on iPad so have to use canvas renderer only.
So, how do I make this model lighter to load and works smoothly on iPad ? and is there any other way to apply colors on every face ? If canvas map as texture can be applied to make the model lighter, how do I build that map with all the colors based on value ?
Update:
After changing library version to r53, vertexColors: THREE.FaceColors and face.color.setRGB( Math.random(), Math.random(), Math.random()), the model displays random color for each face on canvas rendering.
So now the issue is applying colors as per requirements (either by canvas map or any feasible solution) and to make the model lighter to load it smoothly on iPad.
I believe this will give you a little bit better performance + if you could come up with some automated method of calculating colors for each angle offset, that you could set hex color directly:
for ( var i = 0; i < tube.faces.length; i ++ ) {
tube.faces[ i ].color.setHex( Math.random() * 0xffffff );
}
As I explained to you in the previous message - three.js - text next to line, using canvas textures will only increase load to you fps if you'll attempt to render so many faces.
If you really want to render 24,000 faces on canvas renderer and still hope that it gonna show up good on an iPad – you are out of your mind!))
Here is the only solution that I can think of for now:
1) Set your tube to only 1 segment.
2) Create 12 canvas elements (for every radius segment) with Width equal to your tube length (see my link above).
3) Now imagine that your 2000 segments you are going to create inside of each canvas. So, you divide your canvas length by 2000 and for every one of the portion of this division you set your calculated color!!! (Just like the Stats() FPS bar shows it’s bar, but you are going to have each bar different color).
4) Then you just apply your colored-bars-canvas-texture to each one of your 12 radius segments and you are good to go!!
This way you’ll only get initial page load (calculating 'em 24,000 colored-bars) and YOUR WHOLE TUBE ONLY GONNA BE 12 FACES!!!
Now, I know your next question is going to be: How I'll pick my faces to show my lines with tag text?
Well, very simple! Just take current face (1 of 12) pick position coordinates and translate them back to your JSON, just the same way you would do with 24,000 faces;)
Hope that helps!