DS=-dsco NAME="Youshi Industrial Park grid addressing and street naming exercise" \
-dsco DESCRIPTION="See $(subst $(HOME)/,https://www.,$(PWD))"
## Author: Dan Jacobson https://www.jidanni.org/
## Copyright: https://www.gnu.org/licenses/gpl.html
## Created: 2026-04-03T22:00:51+0000
## Last-Updated: 2026-06-02T15:59:13+0000
## Update #: 1853
D=$(HOME)/Downloads
DD=$D/d/pandoc
TR=$(DD)/translations/zh-TW.yaml
### Conference documentation part. They wanted a DOCX.
youshi.browse:
youshi_zh.html:
youshi_zh.browse:
slides.show:
%.show:%.html; $${BROWSER?} $<
%.math_check:%.txt;! fgrep -nH \$$ $< #Math didn't show up in DOCX so don't dare use it.
zh2en0:; @perl -pwl \
-e 's/(\d+)至(\d+)號/Nos. $$1-$$2/g; s/對面/opposite /g;' \
-e 's/(\d+)路/Rd. $$1/g; s/(\d+)號/No. $$1/g;'
zh2en1:; @perl -nwl \
-e '/(\d+)號/ && push @p, "No. $$1";' \
-e '/(\d+)衖/ && push @p, "Sub alley $$1";' \
-e '/(\d+)弄/ && push @p, "Alley $$1";' \
-e '/(\d+)巷/ && push @p, "Lane $$1";' \
-e '/(\d+)街/ && push @p, "Street $$1";' \
-e '/(\d+)路/ && push @p, "Road $$1";' \
-e 'print qq{@p}; undef @p;' #one address per input line please
en2zh1:; @perl -nwl \
-e 'push @p,"$$1路" if /Road\s+(\d+)/;' \
-e 'push @p,"$$1街" if /Street\s+(\d+)/;' \
-e 'push @p,"$$1巷" if /Lane\s+(\d+)/;' \
-e 'push @p,"$$1弄" if /Alley\s+(\d+)/;' \
-e 'push @p,"$$1衖" if /Sub alley\s+(\d+)/;' \
-e 'push @p,"$$1號" if /(?:No\.|№)\s+(\d+)/;' \
-e 'print @p; undef @p;' #one address per input line please
reference-docx-proof-of-lack:
pandoc --print-default-data-file reference.docx |\
pandoc --from docx+styles --to markdown
# https://github.com/jgm/citeproc/blob/master/locales/zh-TW.xml
# https://forums.zotero.org/post/editdiscussion/0/262465
#Workaround
newzh=\
\
\
\
取自\
\
\
#https://www.zotero.org/styles/apa-6th-edition
dan6.csl:$(DD)/apa-6th-edition.csl
perl -pwle 'if(/xml:lang="ro">/){print q( $(newzh));}' $< > $@
BB=--csl dan6.csl --citeproc --bibliography=dan.bib --metadata link-citations=true
mini=積丹尼-路號門牌弧形網-\(摘要\)
quanwen=積丹尼-路號門牌弧形網-\(全文\)
mini.txt:youshi_z0.txt; perl -nwle \
'exit if /::: extra/; next if /House|Road|Folded/; print;' $< > $@
mini.docx: BB=
mini.docx: fils=indent.lua
mini:mini.docx; mv $< $D/$(mini).docx; : now go look at it
xmllook:youshi.docx
set -eu; m=/tmp/gg; mkdir $$m; mv $< $$m; cd $$m && unzip $<
find /tmp/gg -ls -type f ! -name \*.png ! -name \*.docx \
-exec tidy -i -xml -q {} \;
docx:youshi.docx
mv $< $D/$(quanwen).docx
: Now launch 'Word' upon $D/$(quanwen).docx
%.fetch:
curl --insecure https://api.github.com/repos/$d/$*/releases/latest | \
grep browser_download_url.*$$(dpkg --print-architecture).deb |\
cut -d \" -f 4 | xargs wget --no-directories --no-verbose --show-progress
%.install:%.fetch
sudo dpkg -i $**.deb
mv -v $**.deb /tmp
%.upgrade:%.install;
pandoc.upgrade:d=jgm;
panache.upgrade:d=jolars
go=$<
#go=$$(wwwize -c $(PWD)/$<\#goal)
%.browse:%.html; $${BROWSER?} $(go)
zap_footnotes.lua:; echo "function Note(el) return {} end" > $@
zap_images.lua:; echo "function Image(el) return {} end" > $@
zap_en-US.lua:; echo > $@ \
"function Div(el) if el.attributes['lang'] == 'en-US' "\
"then return {} end return el end"
extra.lua:
echo >$@ 'function Div(el) if el.classes[1] =='\
'"extra" then return {} end return el end'
Linter=%.linted
$(Linter):%.txt
panache lint --message-format short --check --flavor pandoc $< && touch $@
pand=--from=markdown+east_asian_line_breaks --output=$@ \
--fail-if-warnings=true --verbose --strip-comments=true $(BB)
fils=extra.lua zap_en-US.lua zap_footnotes.lua indent.lua
pzh=--standalone=true $(addprefix --lua-filter=,$(fils)) $< \
--metadata=Abstract-title:摘要 \
--metadata=lang:zh-TW --data-dir=$(DD) $(pand)
$(dir $(TR)):; mkdir -p $@
$(TR):; pandoc --output $@ --print-default-data-file=translations/zh-Hant.yaml
%_z0.txt:%.txt %.math_check
perl -Mutf8 -C -nwle \
'BEGIN{use warnings q!FATAL!; $$a=0};' \
-e 's/^## /$$&.substr(q{壹貳參肆伍},$$a++,1).q{、}/e if $$a < 5;' \
-e 's!(^#.*\S)\s*/.*!$$1!;' \
-e 'next if /^ #en_on/../^ #en_off/; print;' \
$< >$@
jing=\#
indent.lua: ii=function Para (para) \
local text = pandoc.utils.stringify(para); \
if $(jing)para.c == 1 and para.c[1].t == "Image" then return para end; \
if $(jing)para.c == 1 and para.c[1].t == "Link" then return para end; \
if (text:find("圖", 1, true) ~= 1) \
and (text:find("www.jidanni", 1, true) == nil) \
then table.insert(para.content, 1, pandoc.Str \
("\u{3000}\u{3000}")) end return para end
indent.lua:; echo '$(ii)' > $@
needs=$(Linter) $(TR) dan.bib dan6.csl
zh_needs=$(needs) $(fils)
youshi_zh.native2:
%.native2:%.native; ascii2uni -Z '\%d' $< > $@ && cat $@
%_zh.native:%_z0.txt $(zh_needs); pandoc $(pzh)
%_zh.html:%_z0.txt $(zh_needs)
pandoc $(pzh)
test $< -ot $@
%.docx:%_z0.txt $(zh_needs)
pandoc $(pzh)
test $< -ot $@
%_ze.txt:%.txt %.math_check; perl -nwle 'next if /#en_/; print;' $< > $@
%.html:%_ze.txt $(needs) dan.js
pandoc $(pand) $< --standalone=true \
--number-sections \
--shift-heading-level-by=-1 --metadata=abstract-title:"摘要 / Abstract" \
--metadata=title:"路號門牌弧形網 A curved road number - house address grid" \
--metadata=author:'$(au)' --include-in-header=dan.js
test $< -ot $@
au=積丹尼 (Dan Jacobson)
### GIS part:
d=$D/d/data
#"84" indicates WGS84 files
SA=1000#The value of our single axis!
road84=$d/youshi_roads.geojson
addr84=$d/youshi_addrs.geojson
include ../../../../utilities/integer_graticule.makefile
h654=house_L654_1456_652.kml
close_view : $(h654) route.kml; $V
medium_view: $(h654) graticule_k_00.kml houses_ai1_named_vertical.kml; $V
far_view : $(h654) graticule_k_00.kml edge.kml; $V
mgr.png:Makefile
make -nd kitty_korner_view | make2graph | dot -Tpng -o $@
$$BROWSER $@ && sleep 11
kitty_korner.vrt: graticule_k_00.gpkg \
house_L2500_2502_500.gpkg house_L500_2500_498.gpkg
$O -single -nln $(basename $@)
kitty_korner.gpkg:kitty_korner.vrt
$R -clipsrc 2495 490 2510 505 -nlt GEOMETRYCOLLECTION
kitty_korner_view: kitty_korner.kml; $V
KLL=../../../../utilities/kml_line_labels
youshi.kmz: graticule_k_00.kml edge.kml
$O -f LIBKML $(DS) --config LIBKML_EXTRUDE_FIELD=
#The following is optional, an attempt to help Google Earth users see the line labels:
unzip $@
set -eu; for i in layers/*grat*; do \
cp $$i $$i.bak; $(KLL) $$i.bak > $$i.xml;\
tidy -xml -i -q $$i.xml > $$i; done
zip --freshen $@
rm -rf layers doc.kml
edge.gpkg:graticule_k_00.gpkg
$R $S "SELECT * FROM $N WHERE name LIKE 1000 OR name LIKE 0"
cross.gpkg:graticule_k_00.gpkg
$R $S "SELECT 2500 AS name, geom FROM $N WHERE name LIKE 2200 "\
"UNION SELECT 7500 AS name, geom FROM $N WHERE name LIKE 700"
quadrants_view: graticule_k_00.kml one_line_x.kml one_line_y.kml; $V
_i = 100 #Interval: How many numbers per city block.
graticule_k_q.% graticule_k_34.% graticule_k_66.%:\
_w = WHERE (0 + name) <= $(SA)
graticule_k_1450.%: _w = WHERE (0 + name) >= $(SA)
graticule_k_00.% : _y = y0=0 yi=$(_i) y1=1000
graticule_k_00.% : _x = x0=1000 xi=$(_i) x1=3200
graticule_k_q.% graticule_k_34.% graticule_k_66.%:\
_x = x0=1100 xi=$(_i) x1=1600
graticule_k_1450.%: _y = y0=200 yi=$(_i) y1=700
graticule_k_1450.%: _x = x0=1450 xi=$(_i) x1=1450
graticule_k_34.% : _y = y0=234 yi=$(_i) y1=534
graticule_k_q.% : _y = y0=628 yi=28 y1=656
graticule_k_66.% : _y = y0=266 yi=$(_i) y1=566
#To show how much of the first quadrant of the Cartesian plane we occupy:
one_line_x.%: params= y0=0 y1=0 x0=-5000 x1=5000 i=500 name=0
one_line_y.%: params= x0=0 x1=0 y0=-5000 y1=5000 i=500 name=0
wt=WHERE \"addr:housenumber\" LIKE '%號'
O=ogrmerge -overwrite_ds -o $@ $^
addr.gpkg:$(addr84) reverse_gcps.txt
$R --optfile reverse_gcps.txt -tps $S \
"SELECT DISTINCT \"addr:street\","\
"\"addr:housenumber\", geometry AS geom FROM "\
"$(notdir $N) WHERE \"addr:housenumber\" LIKE '%號'"
original_addresses.gpkg:addr.gpkg
$R -nlt POINT $S "SELECT DISTINCT printf('%s%c%s',\"addr:street\","\
"CHAR(10), \"addr:housenumber\") AS name, geom FROM $(notdir $N) $(wt)"
house_choices.gpkg:addr.gpkg #No AI, just suggestions for a person to choose from
$R -nlt POINT $S \
"SELECT DISTINCT printf('%d-%d%c%d|%d',floor(x(geom)),ceiling(x(geom)),"\
"CHAR(10),floor(y(geom)),ceiling(y(geom))) AS name, geom FROM $(notdir $N)"
blocks84.gpkg:graticule_k_00.gpkg gcps.txt
$R --optfile gcps.txt -tps -a_srs EPSG:4326 $S \
"SELECT geom, name, ST_AsText(geom) AS specs FROM $N"
mm=SELECT FORMAT('%4d路(%s): %.0fm',name,specs,$(1)(ST_Length(geom,1))) \
AS minmax FROM $N WHERE name $(2) $(SA)
block_lengths:blocks84.gpkg #100 house numbers could equal how many meters?
ogr2ogr /vsistdout/ $< -f CSV -lco STRING_QUOTING=IF_NEEDED $S \
"$(call mm,min,<=) UNION $(call mm,max,<=) "\
"UNION $(call mm,min,>=) UNION $(call mm,max,>=)"
down=CAST(Y(geom)/$(_i) AS INTEGER) * $(_i)
ca=CAST(X(geom) AS INTEGER)
oe=IIF(TRUNC(X(geom)) % 2 != q, $(ca), $(ca) + 1)
even=$(subst q,0,$(oe))
odd =$(subst q,1,$(oe))
snap_within=15 #We are just going for the low hanging fruit. (Easy cases.)
houses_ai0.gpkg:addr.gpkg
$R -nlt POINT $S \
"SELECT DISTINCT geom, $(down) AS road,"\
"$(even) AS number FROM $N WHERE Y(geom) % $(_i) <= $(snap_within) UNION "\
"SELECT DISTINCT geom, $(down)+$(_i) AS road,"\
"$(odd) AS number FROM $N WHERE Y(geom) % $(_i) >= $(_i) - $(snap_within)"
impossible = X(geom) < $(SA) OR Y(geom) > $(SA) OR Y(geom) < 0 #Beyond $(SA)
houses_ai1.gpkg:houses_ai0.gpkg
$R -nlt POINT $S "SELECT * FROM $N WHERE NOT ($(impossible))"
impossible_addresses.gpkg:addr.gpkg #Points beyond the edge, where (x,y) != (y,x)
$R -nlt POINT $S \
"SELECT geom, FORMAT('%.1f/%.1f', X(geom), Y(geom)) AS name "\
"FROM $N WHERE $(impossible)"
#road and house number stacked vertically (one column) in label:
%_named_vertical.gpkg:%.gpkg
$R -nlt POINT $S \
"SELECT geom, FORMAT('%5d路%c%d號', road, CHAR(10), number) AS name FROM $N"
#road and house numbers in one row, horizontally in label:
%_named_horizontal.gpkg:%.gpkg
$R -nlt POINT $S \
"SELECT geom, FORMAT('%d路%d號', road, number) AS name FROM $N"
gz=SELECT geometry AS geom, FORMAT('%d/%d', field_2, field_3) AS name FROM $N
gcps84.gpkg:gcps.txt
$R -if CSV -nlt POINT \
-oo X_POSSIBLE_NAMES=field_4 -oo Y_POSSIBLE_NAMES=field_5 $S "$(gz)"
gcps.gpkg:gcps.txt
$R -if CSV -nlt POINT \
-oo X_POSSIBLE_NAMES=field_2 -oo Y_POSSIBLE_NAMES=field_3 $S "$(gz)"
road_nodes_0_84.gpkg:$(road84)
$R -explodecollections -nlt POINTS $S \
"SELECT ST_DissolvePoints(GEOMETRY) AS geom,name FROM $(notdir $N)"
road_nodes84.gpkg:road_nodes_0_84.gpkg #These are all the OSM nodes in all the roads
$R -nlt POINTS $S "SELECT DISTINCT geom, name FROM $N GROUP BY geom, name"
road_nodes_values84.vrt:road_nodes84.gpkg road_values.csv; $O
road_nodes_values84.gpkg:road_nodes_values84.vrt
$R $S "SELECT DISTINCT road_values.value + 0 AS name,"\
"geom FROM road_values, road_nodes84 "\
"WHERE road_nodes84.name LIKE road_values.name ORDER BY geom"
intersections84.gpkg:road_nodes_values84.gpkg
$R $S "SELECT t1.geom as geom, t1.name AS n1, t2.name AS n2, "\
"t1.name || '/' || t2.name AS name "\
"FROM $N AS t1, $N AS t2 WHERE t1.geom = t2.geom "\
"AND NOT t1.name = t2.name GROUP BY t1.geom"
reverse_gcps.txt:gcps.txt
ogr2ogr -if CSV -of CSV /vsistdout/ $< \
-lco SEPARATOR=SPACE -lco STRING_QUOTING=IF_NEEDED -sql \
"SELECT field_1, field_4, field_5, field_2, field_3 FROM $N" |\
grep -- -gcp > $@
gcps.txt:intersections84.gpkg extra_gcps.txt
ogr2ogr -of CSV /vsistdout/ $< -lco \
STRING_QUOTING=IF_NEEDED -lco SEPARATOR=SPACE $S \
"SELECT '-gcp', n2 AS x, n1 AS y,"\
"x(geom) AS lon, y(geom) AS lat FROM $N ORDER BY geom"|\
perl -anwle 'next unless /\d/; print;' > $@
cat extra_gcps.txt >> $@
extra_gcps=\
1000 1000 24.412002 120.645589 \
1000 700 24.409145 120.643824
extra_gcps.txt:
set $(extra_gcps); while test $$# -gt 0; do \
echo -gcp $$1 $$2 $$4 $$3; shift 4; done > $@
route.gpkg: #How to get to house 654
ogr2ogr -nln $(basename $@) $@ :memory: -nlt LINESTRING $S \
"SELECT ST_GeomFromText('LINESTRING(1250 700, 1600 700)') AS geom, 700 AS name UNION "\
"SELECT ST_GeomFromText('LINESTRING(1600 700, 1650 700)') AS geom, 700 AS name UNION "\
"SELECT ST_GeomFromText('LINESTRING(1600 700, 1600 600)') AS geom, 1600 AS name UNION "\
"SELECT ST_GeomFromText('LINESTRING(1600 600, 1450 600)') AS geom, 600 AS name UNION "\
"SELECT ST_GeomFromText('LINESTRING(1450 600, 1450 656)') AS geom, 1450 AS name UNION "\
"SELECT ST_GeomFromText('LINESTRING(1450 656, 1525 656)') AS geom, 656 AS name UNION "\
"SELECT ST_GeomFromText('LINESTRING(1450 656, 1375 656)') AS geom, 656 AS name"
GPX1= set -eu; set -- $$(ogrinfo -q $<); shift 2; case $$@ in \(Point\)) n=POINT;;\
*Multi\ Line\ String*) n=MULTILINESTRING;; *Line*) n=LINESTRING;;\
*Polygon*|*Multi*) echo No $$@ allowed in GPX! 1>&2; exit 67;; \
*) echo $< --\> $@: Not ready for type \"$$@\" 1>&2; exit 54;; esac;\
$R -nlt $$n $S 'SELECT name, geom AS geometry FROM "$N"'
KML1= $R $S 'SELECT name || "" AS name, geom FROM "$N"' -f LIBKML -dim XY
%84.gpx:%84.gpkg; $(GPX1) #WGS84
%84.kml:%84.gpkg; $(KML1) #WGS84
%.gpx:%.gpkg gcps.txt; $(GPX1) --optfile gcps.txt -tps
%.kml:%.gpkg gcps.txt; $(KML1) --optfile gcps.txt -tps
V= > /tmp/$(VIEWER)errs.txt $(VIEWER) $^ & sleep 11h
S=-dialect SQLite -sql
R=ogr2ogr $@ $< -nln $(basename $@)
N=$(basename $<)
VIEWER=viking
MAKEFLAGS+=--warn-undefined-variables --no-builtin-variables
house_L%.gpkg: #Usage: house_L