├── .gitignore ├── img.jpg ├── index.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanshack/retreats-from-streets/HEAD/img.jpg -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { Client } = require('pg'); 2 | const { waterfall } = require('async'); 3 | const { eachSeries } = require('async'); 4 | 5 | const tableName = "streets_retreats"; 6 | const polygonTable = "city_blocks"; 7 | // Your setting here 8 | const client = new Client({ 9 | user: // e.g. 'postgres', 10 | host: // e.g. '127.0.0.1', 11 | database: // e.g.'belgium-osm', 12 | password: 'YOUR_PASSWORD', 13 | port: // e.g. 5432, 14 | }) 15 | client.connect(); 16 | 17 | // this is the main query 18 | const streetBlocksQuery = ` 19 | WITH 20 | 21 | polygon AS ( 22 | 23 | SELECT * 24 | FROM ${polygonTable} 25 | WHERE id = $$ 26 | 27 | ), 28 | 29 | polygon_within_city AS ( 30 | 31 | SELECT ST_Intersection( cgeom , pgeom ) AS pgeom 32 | FROM city_polygon, polygon 33 | 34 | ), 35 | 36 | candiates AS ( 37 | 38 | SELECT (ST_dumppoints(I_Grid_Point_Distance(pgeom, 5, 5))).geom AS cgeom 39 | FROM polygon_within_city 40 | 41 | ), 42 | 43 | lines AS ( 44 | 45 | SELECT ST_Boundary(pgeom) As lgeom 46 | FROM polygon 47 | ), 48 | 49 | distance AS( 50 | 51 | SELECT ST_Distance(ST_Transform(lgeom,4326)::geography, ST_Transform(cgeom,4326)::geography) As geom, candiates.cgeom 52 | FROM lines,candiates 53 | ORDER BY geom DESC 54 | LIMIT 1 55 | 56 | ), 57 | 58 | circle AS ( 59 | 60 | SELECT 61 | ST_Transform( 62 | ST_Buffer( 63 | ST_Transform(cgeom,4326)::geography, 64 | geom 65 | )::geometry 66 | ,3857) AS cgeom 67 | FROM distance 68 | 69 | ) 70 | 71 | INSERT INTO ${tableName} ( 72 | SELECT * 73 | FROM distance,circle 74 | ); 75 | 76 | `; 77 | 78 | 79 | waterfall([ 80 | function(callback) { 81 | // Create a new table 82 | const createTableQuery = ` 83 | DROP TABLE IF EXISTS ${tableName}; 84 | CREATE TABLE ${tableName} (distance float8, pgeom geometry,cgeom geometry);`; 85 | client.query(createTableQuery, (err, res) => { 86 | callback(null); 87 | }) 88 | 89 | }, 90 | function(callback) { 91 | // select all the city blocks 92 | const polyonQuery = `SELECT * FROM ${polygonTable}`; 93 | client.query(polyonQuery, (err, res) => { 94 | const allPolygons = res.rows; 95 | callback(null, allPolygons); 96 | }) 97 | 98 | }, 99 | function(allPolygons, callback) { 100 | // make a query for each city block 101 | eachSeries(allPolygons, function(file, callbackEach) { 102 | console.log("city block ", file.id); 103 | const queryWithId = streetBlocksQuery.replace('$$',file.id); 104 | client.query(queryWithId, (err, res) => { 105 | if(allPolygons.length === file.id){ 106 | callback(null, "done") 107 | }else{ 108 | callbackEach(); 109 | } 110 | 111 | }) 112 | 113 | }); 114 | 115 | } 116 | ], function (err, result) { 117 | console.log("DONE"); 118 | client.end(); 119 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## How to make a "Retreats from Streets" map 2 | 3 | This tutorial will show you how to calculate the furthest distance away from streets within [city blocks](https://en.wikipedia.org/wiki/City_block) i.e. areas completely surrounded by streets. The final data can be used to make interactive maps like e.g. [this](https://hanshack.com/rueckzugsorte/) or this [this](https://hanshack.com/retreats/brussels). 4 | 5 | ![preview](img.jpg "") 6 | 7 | ### Requirements 8 | * Some basic knowledge of PostgreSQL/PostGIS 9 | * Some basic knowledge of how to run a Node.js script 10 | * You need to know how to import OpenStreetMap data to PostgreSQL/PostGIS (with e.g. osm2pgsql) 11 | * Some understanding of OpenStreetMap data and Javascript is useful if you want to make changes to the queries and code. 12 | 13 | 14 | ### Main steps 15 | 1. Import OSM data 16 | 2. Compute city blocks - run some SQL queries 17 | 3. Compute furthest place away from streets for each city block - run a Node.js script 18 | 19 | ##### 1. Import OpenStreetMap data 20 | Get some OSM data from e.g. [Geofabrik](http://download.geofabrik.de/) and import it to PostgreSQL. I used osm2pgsql to import the file **belgium-latest.osm.pbf** which includes all the OSM data for Belgium. 21 | 22 | 23 | 24 | ##### 2. Compute the city blocks 25 | We have to run several SQL queries to compute the city blocks. I broke them down to several queries so its easier to understand whats going on. 26 | 27 | 28 | 1. Select an area of interest (in this example I chose Brussels) 29 | 30 | 31 | ```sql 32 | DROP TABLE IF EXISTS city_polygon; 33 | CREATE TABLE city_polygon(cgeom geometry); 34 | 35 | INSERT INTO city_polygon ( 36 | SELECT ST_Union(way) 37 | FROM planet_osm_polygon 38 | WHERE name = 'Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest' 39 | ); 40 | ``` 41 | 42 | 2. Buffer the area of interest by about 2000 meters. This will be useful later on so we do not have to run calculations on stuff we are not interested in. 43 | 44 | ```sql 45 | DROP TABLE IF EXISTS city_polygon_buffer; 46 | CREATE TABLE city_polygon_buffer (cgeom geometry); 47 | 48 | INSERT INTO city_polygon_buffer ( 49 | SELECT 50 | ST_Transform( 51 | ST_Buffer( 52 | ST_Transform(cgeom,4326)::geography, 53 | 2000 54 | )::geometry 55 | ,3857) 56 | FROM city_polygon 57 | ); 58 | ``` 59 | 60 | 3. Select all the streets that are allowed for cars within our buffered area of interest. This would also be the place where to include other roads types and/or train tracks etc. 61 | 62 | ```sql 63 | DROP TABLE IF EXISTS streets; 64 | CREATE TABLE streets (sgeom geometry); 65 | 66 | INSERT INTO streets ( 67 | SELECT way 68 | FROM planet_osm_line,city_polygon_buffer 69 | WHERE 70 | (highway='motorway' or highway='motorway_link'or 71 | highway='trunk' or highway='trunk_link'or 72 | highway='primary' or highway='primary_link'or 73 | highway='secondary' or highway='secondary_link'or 74 | highway='tertiary' or highway='tertiary_link'or 75 | highway='unclassified' or highway='unclassified_link'or 76 | highway='residential' or highway='residential_link'or 77 | highway='living_street') AND (tunnel NOT IN ('yes') or tunnel IS NULL) 78 | AND ST_Intersects( way , cgeom ) 79 | ); 80 | 81 | ``` 82 | 83 | 4. Buffer the streets by 1.3 meters so they will be 2.6 meters wide (the max permitted width for cars in Europe) and union them to one big polygon. 84 | 85 | 86 | ```sql 87 | DROP TABLE IF EXISTS streets_union; 88 | CREATE TABLE streets_union (sgeom geometry); 89 | 90 | INSERT INTO streets_union ( 91 | SELECT ST_Union( 92 | ST_Transform( 93 | ST_Buffer(ST_Transform(sgeom,4326)::geography,1.3)::geometry 94 | ,3857) 95 | ) 96 | FROM streets 97 | ); 98 | ``` 99 | 100 | 5. Intersect the streets with the city polygon to make the city blocks. Then only select the blocks that are intersecting (within and partly within) the city polygon. A primary key is also added to the bocks for later use. 101 | 102 | ```sql 103 | DROP TABLE IF EXISTS city_blocks; 104 | CREATE TABLE city_blocks (pgeom geometry); 105 | WITH 106 | 107 | blocks AS ( 108 | SELECT (ST_Dump(ST_SymDifference(cgeom,sgeom))).geom AS bgeom 109 | FROM city_polygon_buffer,streets_union 110 | ) 111 | 112 | INSERT INTO city_blocks ( 113 | SELECT bgeom 114 | FROM city_polygon,blocks 115 | WHERE ST_Intersects( cgeom , bgeom ) 116 | ); 117 | 118 | ALTER TABLE city_blocks ADD COLUMN id SERIAL PRIMARY KEY; 119 | 120 | ``` 121 | 122 | 6. Add the following function called I_Grid_Point_Distance which allows you to make point grids within a polygon. We need this later on. I luckily found this function on [gis.stackexchange.com]( https://gis.stackexchange.com/questions/4663/how-to-create-regular-point-grid-inside-a-polygon-in-postgis). 123 | 124 | ```sql 125 | CREATE OR REPLACE FUNCTION public.I_Grid_Point_Distance(geom public.geometry, x_side decimal, y_side decimal) 126 | RETURNS public.geometry AS $BODY$ 127 | DECLARE 128 | x_min decimal; 129 | x_max decimal; 130 | y_max decimal; 131 | x decimal; 132 | y decimal; 133 | returnGeom public.geometry[]; 134 | i integer := -1; 135 | srid integer := 4326; 136 | input_srid integer; 137 | BEGIN 138 | CASE st_srid(geom) WHEN 0 THEN 139 | geom := ST_SetSRID(geom, srid); 140 | ----RAISE NOTICE 'No SRID Found.'; 141 | ELSE 142 | ----RAISE NOTICE 'SRID Found.'; 143 | END CASE; 144 | input_srid:=st_srid(geom); 145 | geom := st_transform(geom, srid); 146 | x_min := ST_XMin(geom); 147 | x_max := ST_XMax(geom); 148 | y_max := ST_YMax(geom); 149 | y := ST_YMin(geom); 150 | x := x_min; 151 | i := i + 1; 152 | returnGeom[i] := st_setsrid(ST_MakePoint(x, y), srid); 153 | <> 154 | LOOP 155 | IF (y > y_max) THEN 156 | EXIT; 157 | END IF; 158 | 159 | CASE i WHEN 0 THEN 160 | y := ST_Y(returnGeom[0]); 161 | ELSE 162 | y := ST_Y(ST_Project(st_setsrid(ST_MakePoint(x, y), srid), y_side, radians(0))::geometry); 163 | END CASE; 164 | 165 | x := x_min; 166 | <> 167 | LOOP 168 | IF (x > x_max) THEN 169 | EXIT; 170 | END IF; 171 | i := i + 1; 172 | returnGeom[i] := st_setsrid(ST_MakePoint(x, y), srid); 173 | x := ST_X(ST_Project(st_setsrid(ST_MakePoint(x, y), srid), x_side, radians(90))::geometry); 174 | END LOOP xloop; 175 | END LOOP yloop; 176 | RETURN 177 | ST_CollectionExtract(st_transform(ST_Intersection(st_collect(returnGeom), geom), input_srid), 1); 178 | END; 179 | $BODY$ LANGUAGE plpgsql IMMUTABLE;` 180 | ``` 181 | 182 | 183 | ##### 3. Compute furthest place away from streets for each city block 184 | 185 | Since I'm not a PostgreSQL expert, I wrote a Node.js script which loops through the city blocks you just created in step two. A point grid by 5x5 meters is laid out within each city block (with the I_Grid_Point_Distance function you added earlier). Then the furthest point away from the polygon outline is queried (the outline is where the street starts). 186 | 187 | The script will add a table to your databank with the furthest points from a street for each block. An additional table with circles is also added which have the points as their center and a radius with the maximum distance to the street. 188 | 189 | Note: Before you run the script, remember to install the dependencies and edit the connection settings to your DB which can be done in the ***index.js*** file. You can find the script in this repo. 190 | 191 | Good luck and please let me know if you made some nice maps! 192 | 193 | 194 | --------------------------------------------------------------------------------