├── .gitignore ├── Makefile ├── README.md ├── carto └── style.mss ├── docker-compose.yml ├── docker ├── Dockerfile.kosmtik └── Dockerfile.mapnik ├── kosmtik ├── .kosmtik-config.yml ├── init.sh └── project.mml ├── scripts ├── ingest_traces.sh └── osm2pgsql.lua └── sql ├── match_trace_segments.sql ├── schema └── 20_init_database.sql └── segmentize_roads.sql /.gitignore: -------------------------------------------------------------------------------- 1 | data/ 2 | tmp/ 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REGION_NAME ?= north-america/us/california/socal-latest.osm.pbf 2 | FILE_REGION_EXPORT := data/$(notdir ${REGION_NAME}) 3 | FILE_WAYS := data/ways.osm.pbf 4 | 5 | GPS_TRACES_SRC_DIR ?= data/traces 6 | 7 | export PG_USER ?= postgis_admin 8 | export PG_PASS ?= some-secret-password-here 9 | export PG_HOST ?= localhost 10 | export PG_PORT ?= 5432 11 | export PG_DB ?= postgis 12 | export PG_URL ?= postgresql://${PG_USER}:${PG_PASS}@${PG_HOST}:${PG_PORT}/${PG_DB} 13 | 14 | .SUFFIXES: .sql 15 | 16 | .PHONY: all 17 | all: ingest-osm segment-roads ingest-traces match-traces-to-segments 18 | 19 | ${FILE_REGION_EXPORT}: 20 | mkdir -p $(dir ${FILE_REGION_EXPORT}) 21 | curl "https://download.geofabrik.de/${REGION_NAME}" -o "${FILE_REGION_EXPORT}" 22 | 23 | ${FILE_WAYS}: ${FILE_REGION_EXPORT} 24 | osmium tags-filter "${FILE_REGION_EXPORT}" \ 25 | --overwrite \ 26 | "wr/highway,path,bicycle,foot" \ 27 | -o "${FILE_WAYS}" 28 | 29 | .PHONY: ingest-osm 30 | ingest-osm: ${FILE_WAYS} scripts/osm2pgsql.lua 31 | osm2pgsql -d ${PG_URL} \ 32 | --create \ 33 | --slim \ 34 | --hstore \ 35 | --number-processes $(shell nproc) \ 36 | --style scripts/osm2pgsql.lua \ 37 | --output=flex \ 38 | ${FILE_WAYS} 39 | 40 | # FIXME: This is extremely not how Makefiles are meant to work. 41 | .PHONY: ingest-traces 42 | ingest-traces: $(wildcard ${GPS_TRACES_SRC_DIR}/*) 43 | ./scripts/ingest_traces.sh ${GPS_TRACES_SRC_DIR} 44 | 45 | .PHONY: segment-roads 46 | segment-roads: ${FILE_WAYS} ./sql/segmentize_roads.sql 47 | psql ${PG_URL} < ./sql/segmentize_roads.sql 48 | 49 | .PHONY: match-traces-to-segments 50 | match-traces-to-segments: ${FILE_WAYS} ./sql/match_trace_segments.sql 51 | psql ${PG_URL} < ./sql/match_trace_segments.sql 52 | 53 | .PHONY: psql-shell 54 | psql-shell: 55 | psql ${PG_URL} 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # unexplored 2 | 3 | Match GPS traces against OpenStreetMap data to find where you haven't gone yet. 4 | 5 | ![example tile output](https://user-images.githubusercontent.com/188935/113249128-0d7b2280-9273-11eb-9fac-1c22d27ad44d.png) 6 | 7 | ## notes 8 | 9 | This is a weekend hack, and setting things up is going to be somewhat manual. If 10 | you want a version of this tool that works _well_, check out 11 | [wandrer.earth](https://wandrer.earth). 12 | 13 | All of the heavy lifting is outsourced to some core parts of the OpenStreetMap 14 | software stack: 15 | 16 | 1. `osmium` for filtering a OSM PBF export 17 | 2. `gpsbabel` for converting FIT files into GPX 18 | 3. `postgis` for geospatial storage + querying 19 | 4. `osm2pgsql` to ingest OSM data into `postgis` 20 | 5. `ogr2ogr` / `gdal` to ingest GPX files into `postgis` 21 | 6. `carto` / `kosmtik` for map styling 22 | 7. `mapnik` to render tiles 23 | 24 | For more information about how these pieces fit together, see 25 | [switch2osm](https://switch2osm.org/). 26 | 27 | Matching GPS traces to OSM paths is done through a rather naive distance 28 | check. A more fully-fledged implementation of this idea would use map-matching, 29 | as provided by a tool like [GraphHopper] or [OSRM]. 30 | 31 | [GraphHopper]: https://github.com/graphhopper/graphhopper#map-matching 32 | [OSRM]: http://project-osrm.org/docs/v5.5.1/api/#match-service 33 | 34 | ## setup 35 | 36 | ``` bash 37 | mkdir -p data/ 38 | 39 | # Any file ending in [.fit, .fit.gz, .gpx, .gpx.gz] will be processed. 40 | cp -R YOUR-GPS-TRACES-DIR/ data/traces/ 41 | 42 | # Bring up our database 43 | docker-compose up postgis -d 44 | 45 | # This does a lot of work, `make` could take a while: 46 | # 47 | # 1. Download region extract from geofabrik 48 | # 2. Process region extract and ingest to database 49 | # 3. Split OSM ways into smaller segments 50 | # 4. Ingest GPS traces to database 51 | # 5. Match each GPS trace point against the ingested segments 52 | make 53 | 54 | # Bring up kosmtik on http://127.0.0.1:6789/ 55 | docker-compose up 56 | ``` 57 | 58 | ## license 59 | 60 | This project reuses some code from GPL-2.0-licensed [osm2pgsql], meaning this 61 | repository falls under the same conditions. 62 | 63 | This program is free software; you can redistribute it and/or modify it under 64 | the terms of the GNU General Public License as published by the Free Software 65 | Foundation; either version 2 of the License, or (at your option) any later 66 | version. 67 | 68 | This program is distributed in the hope that it will be useful, but WITHOUT ANY 69 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 70 | PARTICULAR PURPOSE. See the GNU General Public License for more details. 71 | 72 | [osm2pgsql]: https://github.com/openstreetmap/osm2pgsql 73 | -------------------------------------------------------------------------------- /carto/style.mss: -------------------------------------------------------------------------------- 1 | @traversed-color: #264653; 2 | @untraversed-color: #e76f51; 3 | @unmapped-color: #FFD505; 4 | 5 | #traversed-paths { 6 | background/line-color: @traversed-color; 7 | background/line-width: 2; 8 | [zoom >= 16] { 9 | background/line-width: 4; 10 | line-color: #fff; 11 | line-width: 2; 12 | line-dasharray: 2,2; 13 | } 14 | } 15 | 16 | #untraversed-paths { 17 | line-color: @untraversed-color; 18 | line-width: 2; 19 | [zoom >= 16] { 20 | line-width: 4; 21 | } 22 | } 23 | 24 | #unmapped-points { 25 | marker-fill: @unmapped-color; 26 | marker-width: 5; 27 | marker-height: 5; 28 | marker-type: ellipse; 29 | } 30 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | services: 3 | postgis: 4 | image: postgis/postgis:12-3.1-alpine 5 | # Kosmtik/Mapnik need a much larger shared memory buffer. 6 | shm_size: 1g 7 | volumes: 8 | - pg_data:/var/lib/postgresql/data 9 | # Note: doing this per-file so we don't lose the 10_something.sql file 10 | # postgis/postgis sets up. 11 | - ./sql/schema/20_init_database.sql:/docker-entrypoint-initdb.d/20_init_database.sql 12 | environment: 13 | - POSTGRES_USER=postgis_admin 14 | - POSTGRES_PASSWORD=some-secret-password-here 15 | - POSTGRES_DB=postgis 16 | ports: 17 | - '5432:5432' 18 | 19 | kosmtik: 20 | build: 21 | context: . 22 | dockerfile: docker/Dockerfile.kosmtik 23 | volumes: 24 | - .:/kosmtik 25 | ports: 26 | - "127.0.0.1:6789:6789" 27 | environment: 28 | - PGHOST=postgis 29 | - PGUSER=mapnik_renderer 30 | 31 | mapnik: 32 | build: 33 | context: . 34 | dockerfile: docker/Dockerfile.mapnik 35 | 36 | volumes: 37 | pg_data: {} 38 | -------------------------------------------------------------------------------- /docker/Dockerfile.kosmtik: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | 3 | # Style dependencies 4 | RUN apt-get update && apt-get install --no-install-recommends -y \ 5 | ca-certificates curl gnupg postgresql-client python3 python3-distutils \ 6 | fonts-hanazono fonts-noto-cjk fonts-noto-hinted fonts-noto-unhinted \ 7 | mapnik-utils nodejs npm ttf-unifont unzip && rm -rf /var/lib/apt/lists/* 8 | 9 | # Kosmtik with plugins, forcing prefix to /usr because bionic sets 10 | # npm prefix to /usr/local, which breaks the install 11 | RUN npm set prefix /usr && npm install -g kosmtik 12 | 13 | WORKDIR /usr/lib/node_modules/kosmtik/ 14 | RUN kosmtik plugins --install kosmtik-overpass-layer \ 15 | --install kosmtik-fetch-remote \ 16 | --install kosmtik-overlay \ 17 | --install kosmtik-open-in-josm \ 18 | --install kosmtik-map-compare \ 19 | --install kosmtik-osm-data-overlay \ 20 | --install kosmtik-mapnik-reference \ 21 | --install kosmtik-geojson-overlay 22 | 23 | RUN mkdir -p /kosmtik 24 | WORKDIR /kosmtik 25 | 26 | USER 1000 27 | CMD sh kosmtik/init.sh 28 | -------------------------------------------------------------------------------- /docker/Dockerfile.mapnik: -------------------------------------------------------------------------------- 1 | FROM overv/openstreetmap-tile-server 2 | -------------------------------------------------------------------------------- /kosmtik/.kosmtik-config.yml: -------------------------------------------------------------------------------- 1 | plugins: 2 | - kosmtik-overlay 3 | - kosmtik-map-compare 4 | - kosmtik-osm-data-overlay 5 | - kosmtik-mapnik-reference 6 | - kosmtik-geojson-overlay 7 | -------------------------------------------------------------------------------- /kosmtik/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export KOSMTIK_CONFIGPATH="kosmtik/.kosmtik-config.yml" 4 | kosmtik serve kosmtik/project.mml --host 0.0.0.0 5 | -------------------------------------------------------------------------------- /kosmtik/project.mml: -------------------------------------------------------------------------------- 1 | scale: 1 2 | metatile: 2 3 | name: unexplored 4 | description: highlight paths not yet traveled 5 | bounds: &world 6 | - -180 7 | - -85.05112877980659 8 | - 180 9 | - 85.05112877980659 10 | center: 11 | - 0 12 | - 0 13 | - 4 14 | format: png 15 | interactivity: false 16 | minzoom: 0 17 | maxzoom: 22 18 | srs: "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over" 19 | 20 | # Various parts to be included later on 21 | _parts: 22 | # Extents are used for tilemill, and don't actually make it to the generated XML 23 | extents: &extents 24 | extent: *world 25 | srs-name: "900913" 26 | srs: "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over" 27 | 28 | osm2pgsql: &osm2pgsql 29 | type: "postgis" 30 | dbname: "postgis" 31 | user: "mapnik_renderer" 32 | password: "todo" 33 | key_field: "" 34 | geometry_field: "geom" 35 | extent: "-20037508,-20037508,20037508,20037508" 36 | 37 | Stylesheet: 38 | - ../carto/style.mss 39 | 40 | Layer: 41 | - id: untraversed-paths 42 | geometry: linestring 43 | <<: *extents 44 | Datasource: 45 | <<: *osm2pgsql 46 | table: |- 47 | ( 48 | SELECT geom 49 | FROM path_segments_traces_view 50 | WHERE num_hits = 0 51 | ) as untraversed_paths 52 | properties: 53 | cache-features: true 54 | minzoom: 13 55 | 56 | - id: traversed-paths 57 | geometry: linestring 58 | <<: *extents 59 | Datasource: 60 | <<: *osm2pgsql 61 | table: |- 62 | ( 63 | SELECT geom 64 | FROM path_segments_traces_view 65 | WHERE num_hits >= 1 66 | ) as traversed_paths 67 | properties: 68 | cache-features: true 69 | minzoom: 11 70 | 71 | - id: traversed-paths 72 | geometry: linestring 73 | <<: *extents 74 | Datasource: 75 | <<: *osm2pgsql 76 | table: |- 77 | ( 78 | SELECT geom 79 | FROM path_segments_traces_view 80 | WHERE num_hits >= 1 AND z_order > 3 81 | ) as traversed_paths 82 | properties: 83 | cache-features: true 84 | minzoom: 1 85 | maxzoom: 11 86 | 87 | - id: unmapped-points 88 | geometry: point 89 | <<: *extents 90 | Datasource: 91 | <<: *osm2pgsql 92 | table: |- 93 | ( 94 | SELECT geom 95 | FROM unmatched_traces 96 | ) as unmapped_points 97 | properties: 98 | cache-features: true 99 | minzoom: 12 100 | -------------------------------------------------------------------------------- /scripts/ingest_traces.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Run trace conversion / ingest in parallel to speed everything up. 4 | 5 | set -e 6 | 7 | FROM_DIR="$1"; shift 8 | MAX_PROCS=$(nproc) 9 | 10 | # Export this so it's available in sub-shells 11 | export PG_CONN="dbname='${PG_DB}' \ 12 | host='${PG_HOST}' \ 13 | port='${PG_PORT}' \ 14 | user='${PG_USER}' \ 15 | password='${PG_PASS}'" 16 | 17 | gunzip_file () { 18 | local src="$1"; shift 19 | local dst="$(dirname $src)/$(basename -s .gz $src)" 20 | if [ -f "$dst" ]; then 21 | return 22 | fi 23 | echo "gunzip_file: $src" 24 | gunzip "$src" 25 | } 26 | 27 | fit_to_gpx () { 28 | local src="$1"; shift 29 | local dst="$src.gpx" 30 | if [ -f "$dst" ]; then 31 | return 32 | fi 33 | 34 | echo "fit_to_gpx: $src" 35 | gpsbabel -i garmin_fit -f "$src" -o gpx -F "$dst" 36 | } 37 | 38 | _psql () { 39 | local command="$1"; shift 40 | psql "${PG_URL}" -tqc "$command" 41 | } 42 | 43 | ogr2ogr_import () { 44 | local f="$1"; shift 45 | 46 | # Skip over files we've already imported. 47 | # 48 | # FIXME: There's a SQL injection here. But you can only attack yourself. 49 | local exists=$(_psql "SELECT 1 FROM trace_files WHERE file_name = '$f';") 50 | if [ ! -z "$exists" ]; then 51 | echo "ogr2ogr_import: already imported: $f" 52 | return 53 | fi 54 | 55 | echo "ogr2ogr_import: $f" 56 | ogr2ogr -update -append \ 57 | -t_srs EPSG:3857 \ 58 | -f PostgreSQL "PG:${PG_CONN}" \ 59 | "$f" \ 60 | track_points 61 | 62 | _psql "INSERT INTO trace_files VALUES ('$f');" 63 | } 64 | 65 | export -f _psql 66 | export -f gunzip_file 67 | export -f fit_to_gpx 68 | export -f ogr2ogr_import 69 | 70 | ls ${FROM_DIR}/*.gz | xargs -n1 -P ${MAX_PROCS} bash -c 'gunzip_file "$@"' _ 71 | ls ${FROM_DIR}/*.fit | xargs -n1 -P ${MAX_PROCS} bash -c 'fit_to_gpx "$@"' _ 72 | ls ${FROM_DIR}/*.gpx | xargs -n1 -P ${MAX_PROCS} bash -c 'ogr2ogr_import "$@"' _ 73 | 74 | echo 'Finished.' 75 | -------------------------------------------------------------------------------- /scripts/osm2pgsql.lua: -------------------------------------------------------------------------------- 1 | -- Using the flex output of osm2pgsql to create a custom table 2 | -- https://osm2pgsql.org/doc/manual.html#the-flex-output 3 | 4 | -- Set of all the tags we care about 5 | included_way_tags = { 6 | 'highway', 7 | 'bicycle', 8 | 'foot', 9 | 'path', 10 | } 11 | 12 | -- Filter these out. Takes precedence over included_way_tags 13 | excluded_way_tags = { 14 | highway = { 'motorway', 'motorway_link', 'ferry' }, 15 | footway = { 'sidewalk' }, 16 | access = { 'no', 'private' }, 17 | bicycle = { 'no' }, 18 | service = { 'parking_aisle', 'alley' }, 19 | [ "access:bicycle" ] = { 'no' }, 20 | } 21 | 22 | function matches_any_key(tags, possible_keys) 23 | for _, k in ipairs(possible_keys) do 24 | if tags[k] ~= nil then 25 | return true 26 | end 27 | end 28 | 29 | return false 30 | end 31 | 32 | function matches_any_values(tags, possible_values) 33 | for key, values in pairs(possible_values) do 34 | local tag_value = tags[key] 35 | for _, val in ipairs(values) do 36 | if val == tag_value then 37 | return true 38 | end 39 | end 40 | end 41 | 42 | return false 43 | end 44 | 45 | function match_way_tags(tags) 46 | -- First match against our inclusive set 47 | if not matches_any_key(tags, included_way_tags) then 48 | return false 49 | end 50 | 51 | -- Then see if we match any exlcuded tags 52 | return not matches_any_values(tags, excluded_way_tags) 53 | end 54 | 55 | likely_paved_tags = { 56 | surface = { 57 | 'asphalt', 58 | 'concrete', 59 | 'concrete:plates', 60 | 'metal', 61 | 'paved', 62 | 'paving_stones', 63 | 'sett', 64 | 'wood', 65 | }, 66 | highway = { 67 | 'trunk', 68 | 'residential', 69 | }, 70 | 71 | } 72 | 73 | likely_unpaved_tags = { 74 | surface = { 75 | 'compacted', 76 | 'dirt', 77 | 'dirt', 78 | 'earth', 79 | 'fine_gravel', 80 | 'grass', 81 | 'grass_paver', 82 | 'gravel', 83 | 'gravel', 84 | 'ground', 85 | 'ice', 86 | 'mud', 87 | 'rock', 88 | 'sand', 89 | 'snow', 90 | 'unpaved', 91 | }, 92 | } 93 | 94 | -- Figure out if the way is paved (probably). 95 | -- 96 | -- Returns: 97 | -- paved: true 98 | -- unpaved: false 99 | -- idk: nil 100 | function is_surface_likely_paved(tags) 101 | if matches_any_values(tags, likely_paved_tags) then 102 | return true 103 | end 104 | if matches_any_values(tags, likely_unpaved_tags) then 105 | return false 106 | end 107 | 108 | return nil 109 | end 110 | 111 | 112 | -- Adapted from osm2pgsql, list of {key, value, z_order} 113 | tag_z_order = { 114 | { 'bridge', 'yes', 10 }, { 'bridge', 'true', 10 }, { 'bridge', 1, 10 }, 115 | { 'tunnel', 'yes', -10 }, { 'tunnel', 'true', -10 }, { 'tunnel', 1, -10 }, 116 | { 'highway', 'minor', 3 }, 117 | { 'highway', 'road', 3 }, 118 | { 'highway', 'unclassified', 3 }, 119 | { 'highway', 'residential', 3 }, 120 | { 'highway', 'tertiary_link', 4 }, 121 | { 'highway', 'tertiary', 4 }, 122 | { 'highway', 'secondary_link', 6 }, 123 | { 'highway', 'secondary', 6 }, 124 | { 'highway', 'primary_link', 7 }, 125 | { 'highway', 'primary', 7 }, 126 | { 'highway', 'trunk_link', 8 }, 127 | { 'highway', 'trunk', 8 }, 128 | { 'highway', 'motorway_link', 9 }, 129 | { 'highway', 'motorway', 9 }, 130 | } 131 | 132 | function get_z_order(tags) 133 | -- The default z_order is 0 134 | z_order = 0 135 | 136 | for i, k in ipairs(tag_z_order) do 137 | if k[2] and tags[k[1]] == k[2] then 138 | z_order = z_order + k[3] 139 | end 140 | end 141 | 142 | return z_order 143 | end 144 | 145 | 146 | table_all_paths = osm2pgsql.define_way_table( 147 | 'all_paths', { 148 | { column = 'name', type = 'text' }, 149 | { column = 'geom', type = 'linestring' }, 150 | { column = 'tags', type = 'hstore' }, 151 | 152 | { column = 'z_order', type = 'integer' }, 153 | { column = 'surface_paved', type = 'boolean' }, 154 | }) 155 | 156 | function osm2pgsql.process_way(object) 157 | if not match_way_tags(object.tags) then 158 | return 159 | end 160 | 161 | table_all_paths:add_row({ 162 | tags = object.tags, 163 | name = object.tags.name, 164 | 165 | z_order = get_z_order(object.tags), 166 | surface = is_surface_likely_paved(object.tags), 167 | 168 | geom = { create = 'line' } 169 | }) 170 | end 171 | -------------------------------------------------------------------------------- /sql/match_trace_segments.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | DROP TABLE IF EXISTS path_segment_traces CASCADE; 4 | 5 | -- TODO: Spend more time on the EXPLAIN 6 | CREATE TABLE path_segment_traces AS 7 | WITH within_bounds_by_track_point AS ( 8 | SELECT 9 | track_points.ogc_fid AS track_point_id, 10 | path_segments.id AS path_segment_id, 11 | row_number() OVER ( 12 | PARTITION BY track_points.ogc_fid 13 | ORDER BY ST_Distance( 14 | path_segments.geom, 15 | track_points.wkb_geometry 16 | ) ASC 17 | ) as row_number 18 | FROM track_points 19 | INNER JOIN path_segments ON ( 20 | path_segments.buffered_geom && track_points.wkb_geometry 21 | ) 22 | ) 23 | SELECT path_segment_id, COUNT(*) as num_hits 24 | FROM within_bounds_by_track_point 25 | WHERE row_number = 1 26 | GROUP BY 1; 27 | 28 | CREATE OR REPLACE VIEW path_segments_traces_view AS 29 | SELECT 30 | path_segments.id as path_segment_id, 31 | osm_id, 32 | geom, 33 | COALESCE(num_hits, 0) as num_hits, 34 | z_order, 35 | surface_paved 36 | FROM path_segments 37 | LEFT JOIN path_segment_traces ON (path_segment_id = path_segments.id); 38 | 39 | 40 | -- This is an expensive query, use a materialized view to speed things up. 41 | DROP MATERIALIZED VIEW IF EXISTS unmatched_traces; 42 | CREATE MATERIALIZED VIEW unmatched_traces AS 43 | SELECT 44 | track_points.wkb_geometry as geom 45 | FROM track_points 46 | WHERE NOT EXISTS ( 47 | SELECT 1 48 | FROM path_segments 49 | WHERE track_points.wkb_geometry && path_segments.buffered_geom 50 | ); 51 | 52 | COMMIT; 53 | -------------------------------------------------------------------------------- /sql/schema/20_init_database.sql: -------------------------------------------------------------------------------- 1 | -- Note: this will run after the Postgis init script sets up the admin 2 | -- user / database for us. 3 | 4 | -- Create user dedicated to Mapnik 5 | CREATE USER mapnik_renderer WITH PASSWORD 'todo'; 6 | 7 | -- Since we don't have the tables / views created yet, grant perms 8 | -- ahead of time. 9 | ALTER DEFAULT PRIVILEGES IN SCHEMA public 10 | GRANT SELECT ON TABLES TO mapnik_renderer; 11 | 12 | -- hstore extension is used by osm2pgsql 13 | CREATE EXTENSION IF NOT EXISTS hstore; 14 | 15 | -- Keep track of imported traces to speed up reruns / allow for 16 | -- incremental uploads. 17 | CREATE TABLE IF NOT EXISTS trace_files ( 18 | file_name TEXT PRIMARY KEY 19 | ); 20 | -------------------------------------------------------------------------------- /sql/segmentize_roads.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | DROP TABLE IF EXISTS path_segments CASCADE; 4 | 5 | CREATE TABLE path_segments AS 6 | WITH segmentized_paths AS ( 7 | SELECT 8 | way_id as osm_id, 9 | ST_Segmentize(geom, 500) as way, 10 | z_order, 11 | surface_paved 12 | FROM public.all_paths 13 | WHERE geom IS NOT NULL 14 | ), series_by_path AS ( 15 | SELECT osm_id, generate_series(1, ST_NPoints(way)-1) as n 16 | FROM segmentized_paths 17 | ) 18 | SELECT 19 | osm_id, 20 | z_order, 21 | surface_paved, 22 | ST_Buffer( 23 | ST_MakeLine( 24 | ST_PointN(way, n), 25 | ST_PointN(way, n+1) 26 | ), 27 | 20 28 | ) as buffered_geom, 29 | ST_MakeLine( 30 | ST_PointN(way, n), 31 | ST_PointN(way, n+1) 32 | ) as geom 33 | FROM series_by_path 34 | INNER JOIN segmentized_paths USING (osm_id); 35 | 36 | -- TODO: Can we do this as part of the CREATE TABLE AS? 37 | ALTER TABLE path_segments ADD COLUMN id SERIAL; 38 | ALTER TABLE path_segments ADD PRIMARY KEY (id); 39 | ALTER TABLE path_segments ALTER COLUMN osm_id SET NOT NULL; 40 | ALTER TABLE path_segments ALTER COLUMN geom SET NOT NULL; 41 | 42 | -- TODO: Should benchmark this, theoretically SP-GiST is better than 43 | -- GiST at overlapping data (such as roads?) 44 | CREATE INDEX path_segments_geom_idx 45 | ON path_segments 46 | USING SPGIST (geom); 47 | CREATE INDEX path_segments_buffered_geom_idx 48 | ON path_segments 49 | USING SPGIST (buffered_geom); 50 | 51 | COMMIT; 52 | --------------------------------------------------------------------------------