Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use mapshaper to simplify a region geometry #732

Merged
merged 4 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions resources/migrations/068-add_regions_name_index.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE regions DROP CONSTRAINT "regions_name_index";
1 change: 1 addition & 0 deletions resources/migrations/068-add_regions_name_index.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE regions ADD CONSTRAINT "regions_name_index" UNIQUE ("country", "name", "admin_level");
3 changes: 3 additions & 0 deletions resources/planwise/plpgsql/isochrones.sql
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS pgrouting;

-- find closest node to a point
CREATE OR REPLACE FUNCTION closest_node (original geometry(point, 4326))
returns integer as $$
Expand Down
3 changes: 3 additions & 0 deletions resources/planwise/plpgsql/patches.sql
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS pgrouting;

-- Copied from pgRouting and modified
-- Original file: src/alpha_shape/sql/alpha_shape.sql
-- Original copyright notice follows
Expand Down
1 change: 1 addition & 0 deletions scripts/friction/load-friction-raster
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ for region in $regions; do
gdalwarp -co COMPRESS=DEFLATE \
-dstnodata -3.4e+38 \
-crop_to_cutline -csql "SELECT the_geom FROM regions WHERE id = ${region}" \
-cblend 1 \
-cutline PG:"dbname=${POSTGRES_DB} host=${POSTGRES_HOST} port=${POSTGRES_PORT} user=${POSTGRES_USER} password=${POSTGRES_PASSWORD}" \
$raster_file $output_file
else
Expand Down
14 changes: 13 additions & 1 deletion scripts/population/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ $ docker-compose run --rm app bash
/app$ scripts/population/load-regions URY Uruguay
```

### Geometry simplification

The script simplifies the regions using mapshaper by a distance between points.
We can adjust this parameter with the environment `INTERVAL_DISTANCE`.

For example to simplify with 120 meters distance

```sh
$ INTERVAL_DISTANCE=120 ./load-regions -f URY Uruguay
```

Here `-f` is used to force updating an existing region.

## Clip friction layer for new regions

The friction raster is clipped to the country-level regions for quicker access
Expand Down Expand Up @@ -46,4 +59,3 @@ INSERT INTO source_set (name, type, unit, raster_file) VALUES ('Uruguay PPP v2b

The important bit of information is the last field. That should be a relative
path from the `DATA_PATH` directory to the downloaded raster file.

81 changes: 66 additions & 15 deletions scripts/population/load-regions
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,35 @@ set -euo pipefail
export PGPASSWORD=$POSTGRES_PASSWORD;

if [ $# -lt 2 ]; then
echo "Usage $0 <ISO> <Country Name>"
echo "Usage $0 [-f] <ISO> <Country Name>"
echo "The option -f forces updating an existing country geometry"
exit 1
fi

COUNTRY_CODE=$1
COUNTRY_NAME=$2
FORCE=
COUNTRY_CODE=
COUNTRY_NAME=

while [ $# -gt 0 ]; do
case $1 in
-f|--force)
FORCE=1
;;
*)
if [ ! -z "$COUNTRY_NAME" ]; then
echo Unknown arguments specified
exit 1;
elif [ ! -z "$COUNTRY_CODE" ]; then
COUNTRY_NAME=$1
else
COUNTRY_CODE=$1
fi
;;
esac
shift
done


#LEVELS=${2:-2,4}
LEVELS=(0,1)
echo " -> Importing $COUNTRY_NAME at levels $LEVELS"
Expand All @@ -18,40 +41,68 @@ echo " -> Importing $COUNTRY_NAME at levels $LEVELS"
REGIONS=$(psql -d $POSTGRES_DB -U $POSTGRES_USER -h $POSTGRES_HOST -p $POSTGRES_PORT -t -A -c "SELECT id, country FROM regions WHERE country = '$COUNTRY_NAME';")
REGIONS_LEN=$(echo $REGIONS | wc -w | tr -d '[[:space:]]')

if [ ${REGIONS_LEN} -gt 0 ]; then
if [ ${REGIONS_LEN} -gt 0 -a -z $FORCE ]; then
echo " -> Regions for country $COUNTRY_NAME already imported."
exit 0
fi

DATA_PATH=${DATA_PATH:-/data}/geojson

mkdir -p ${DATA_PATH}
mkdir -p "${DATA_PATH}"

# if the folder (still) doesn't exist then exit with error
if [[ ! -e ${DATA_PATH}/$COUNTRY_CODE ]]; then
echo " -> GeoJSON folder not found for country $COUNTRY_NAME"
exit 1
fi

# Simplification parameter
# From mapshaper wiki https://github.com/mbloch/mapshaper/wiki/Command-Reference#-simplify
#
# interval= Specify simplification amount in units of distance.
# Uses meters when simplifying unprojected datasets in 3D space,
# otherwise uses the same units as the source data.
# In our case since our GeoJSON are not projected (ie. use WGS84 coordinates)
# then this means the resolution for the simplified shape will be in meters.

INTERVAL_DISTANCE=${INTERVAL_DISTANCE-100}

IFS=',';
for i in $LEVELS; do
FILE=${DATA_PATH}/${COUNTRY_CODE}/${COUNTRY_CODE}_adm${i}.geojson
echo -n " -> Processing $FILE ... "

OFILE=$FILE
if [[ ! -z $INTERVAL_DISTANCE ]]; then
# Simplify using distance
OFILE=${DATA_PATH}/${COUNTRY_CODE}/${COUNTRY_CODE}_adm${i}_${INTERVAL_DISTANCE}.geojson

mapshaper "$FILE" -simplify interval=$INTERVAL_DISTANCE -o "$OFILE"

SIZE=$(stat --printf="%s" "$FILE")
OSIZE=$(stat --printf="%s" "$OFILE")

echo " -> Finished file size went from $SIZE to $OSIZE"
fi

psql -q -d $POSTGRES_DB -U $POSTGRES_USER -h $POSTGRES_HOST -p $POSTGRES_PORT << SQL_SCRIPT

WITH data AS (SELECT \$$`cat $FILE`\$$::json AS fc)
WITH data AS (SELECT \$$`cat "$OFILE"`\$$::json AS fc)
INSERT INTO "regions" (country, name, admin_level, the_geom)
SELECT
'${COUNTRY_NAME}',
feat#>>'{properties,name}' AS name,
${i},
ST_SetSRID(ST_CollectionExtract(ST_Multi(ST_GeomFromGeoJSON(feat->>'geometry')), 3), 4326) as the_geom
FROM (
SELECT json_array_elements(fc->'features') AS feat
FROM data
) AS f;
SELECT
'${COUNTRY_NAME}',
feat#>>'{properties,name}' AS name,
${i},
ST_SetSRID(ST_CollectionExtract(ST_Multi(ST_GeomFromGeoJSON(feat->>'geometry')), 3), 4326) as the_geom
FROM (
SELECT json_array_elements(fc->'features') AS feat
FROM data
) AS f
ON CONFLICT ON CONSTRAINT regions_name_index DO
UPDATE SET the_geom=excluded.the_geom;

SQL_SCRIPT

# TODO: print errors
echo " done!"
done;
Expand Down