Ran the full stack (docker now available via sg docker): - postgis/postgis:16-3.4 up, both extracts imported: northeast 291,000 POIs (from ~/osm-build/data/northeast.osm.pbf), colombia 228,446 POIs - spatiald live on :5005, proxied by the mock server (/spatial/*, /spatial-status); UI badge + poi_near tool activate automatically Fixes discovered by running against real data: - osm2pgsql image: osm2pgsql/osm2pgsql does not exist on Docker Hub — use iboates/osm2pgsql, bypass its DB-probing entrypoint (--entrypoint osm2pgsql), password via PGPASSWORD (this build's --password flag forces an interactive prompt) - osm2pgsql 2.x schema: planet_osm_point/_polygon (not _node/_way), geometry in EPSG:3857 'way' column, -k hstore for opening_hours/ fee/website/addr:*, refresh_poi() dedupes relation polygons and uses hstore -> (not ->>) - compose file downgraded to v1-compatible 3.8 (host has docker-compose 1.29, no v2 plugin); OSM_DIR exported by import.sh - spatiald: proper SQL parameterization (@@i@@ tokens, body .. shifted past the filter args — the first renumber attempt was unsound), ST_Distance(geography) instead of the geometry-only ST_Distance_Sphere, 4-arg ST_DWithin for the corridor band, WKT in lng lat order, kind= accepts exact (amenity=restaurant), bare tag value (restaurant) or family (tourism) - mock server /spatial proxy: forward u.search (was dropping it) - app.js poi_near: 'points' param, semicolons must be %3B-encoded (Go url.Parse drops the tail of a value containing a raw ';') - README: osm2pgsql 2.x data notes + kind semantics
116 lines
5.3 KiB
PL/PgSQL
116 lines
5.3 KiB
PL/PgSQL
-- PostGIS schema for the trip planner (runs once, on first container boot).
|
|
-- osm2pgsql loads the standard planet_osm_* tables; we then materialize the
|
|
-- POI points we query on, with a GIST index for ST_DWithin / KNN.
|
|
|
|
CREATE EXTENSION IF NOT EXISTS postgis;
|
|
-- osm2pgsql --hstore puts every tag that has no dedicated column here
|
|
CREATE EXTENSION IF NOT EXISTS hstore;
|
|
|
|
-- Named data sets (so multi-extract setups — e.g. northeast + colombia — can
|
|
-- be imported into one DB and disambiguated).
|
|
CREATE TABLE IF NOT EXISTS spatial_extract (
|
|
name text PRIMARY KEY,
|
|
imported_at timestamptz NOT NULL DEFAULT now(),
|
|
bounds box, -- from osmium (in degrees)
|
|
poi_count bigint
|
|
);
|
|
|
|
-- Searchable POIs: every named point/polygon of the interesting kinds.
|
|
-- osm2pgsql 2.x pgsql output schema: planet_osm_point / planet_osm_polygon,
|
|
-- geometry in EPSG:3857 in a column called `way`; tags without a dedicated
|
|
-- column (opening_hours, fee, website, addr:*) live in the hstore `tags`.
|
|
CREATE TABLE IF NOT EXISTS poi (
|
|
extract text NOT NULL REFERENCES spatial_extract(name) ON DELETE CASCADE,
|
|
osm_id bigint NOT NULL,
|
|
osm_type char NOT NULL, -- 'N' | 'W'
|
|
name text NOT NULL,
|
|
kind text NOT NULL, -- dominant tag: amenity=, tourism=, …
|
|
amenity text,
|
|
tourism text,
|
|
shop text,
|
|
leisure text,
|
|
historic text,
|
|
place text,
|
|
opening_hours text,
|
|
fee text, -- OSM fee tag / fee:* values
|
|
website text,
|
|
addr_city text,
|
|
geom geometry(Point, 4326) NOT NULL, -- reprojected from 3857
|
|
PRIMARY KEY (extract, osm_type, osm_id)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS poi_geom_gist ON poi USING GIST (geom);
|
|
CREATE INDEX IF NOT EXISTS poi_kind ON poi (extract, kind);
|
|
CREATE INDEX IF NOT EXISTS poi_name_trgm ON poi USING GIN (to_tsvector('simple', name));
|
|
|
|
-- pgvector (semantic POI index) is a later milestone: it needs the vector
|
|
-- package in the image (postgis/postgis doesn't ship it), so it gets its own
|
|
-- init script + embedding importer instead of living in this base schema.
|
|
|
|
-- Helper: refresh the poi table from a freshly loaded extract.
|
|
-- (Invoked by import.sh; kept here so the SQL lives in one place.)
|
|
CREATE OR REPLACE FUNCTION refresh_poi(p_extract text) RETURNS void AS $$
|
|
BEGIN
|
|
DELETE FROM poi WHERE extract = p_extract;
|
|
INSERT INTO poi (extract, osm_id, osm_type, name, kind, amenity, tourism, shop,
|
|
leisure, historic, place, opening_hours, fee, website, addr_city, geom)
|
|
WITH cand AS (
|
|
SELECT 'N'::char AS t, n.osm_id, n.name, n.amenity, n.tourism, n.shop,
|
|
n.leisure, n.historic, n.place,
|
|
n.tags->'opening_hours' AS opening_hours,
|
|
COALESCE(n.tags->'fee', n.tags->'fee:tourism') AS fee,
|
|
COALESCE(n.tags->'website', n.tags->'url') AS website,
|
|
n.tags->'addr:city' AS addr_city,
|
|
ST_Transform(n.way, 4326) AS geom
|
|
FROM planet_osm_point n
|
|
WHERE n.name IS NOT NULL AND n.name <> ''
|
|
AND (n.amenity IS NOT NULL OR n.tourism IS NOT NULL OR n.shop IS NOT NULL
|
|
OR n.leisure IS NOT NULL OR n.historic IS NOT NULL OR n.place IS NOT NULL)
|
|
UNION ALL
|
|
-- relation-derived polygons repeat per relation (osm_id negated) —
|
|
-- keep one row per way id
|
|
SELECT * FROM (
|
|
SELECT DISTINCT ON (w.osm_id)
|
|
'W'::char AS t, w.osm_id, w.name, w.amenity, w.tourism, w.shop,
|
|
w.leisure, w.historic, w.place,
|
|
w.tags->'opening_hours' AS opening_hours,
|
|
COALESCE(w.tags->'fee', w.tags->'fee:tourism') AS fee,
|
|
COALESCE(w.tags->'website', w.tags->'url') AS website,
|
|
w.tags->'addr:city' AS addr_city,
|
|
ST_Centroid(ST_Transform(w.way, 4326)) AS geom
|
|
FROM planet_osm_polygon w
|
|
WHERE w.name IS NOT NULL AND w.name <> ''
|
|
AND (w.amenity IS NOT NULL OR w.tourism IS NOT NULL OR w.shop IS NOT NULL
|
|
OR w.leisure IS NOT NULL OR w.historic IS NOT NULL)
|
|
ORDER BY w.osm_id
|
|
) w
|
|
)
|
|
SELECT p_extract, osm_id, t, name,
|
|
COALESCE(
|
|
CASE WHEN amenity IS NOT NULL THEN 'amenity=' || amenity
|
|
END,
|
|
CASE WHEN tourism IS NOT NULL THEN 'tourism=' || tourism
|
|
END,
|
|
CASE WHEN shop IS NOT NULL THEN 'shop=' || shop
|
|
END,
|
|
CASE WHEN leisure IS NOT NULL THEN 'leisure=' || leisure
|
|
END,
|
|
CASE WHEN historic IS NOT NULL THEN 'historic=' || historic
|
|
END,
|
|
CASE WHEN place IS NOT NULL THEN 'place=' || place
|
|
END
|
|
),
|
|
amenity, tourism, shop, leisure, historic, place,
|
|
opening_hours, fee, website, addr_city, geom
|
|
FROM cand
|
|
WHERE COALESCE(amenity, tourism, shop, leisure, historic, place) IS NOT NULL;
|
|
|
|
UPDATE spatial_extract
|
|
SET poi_count = (SELECT count(*) FROM poi WHERE extract = p_extract),
|
|
imported_at = now()
|
|
WHERE name = p_extract;
|
|
|
|
ANALYZE poi;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|