This commit is contained in:
s3lph 2022-10-08 21:17:31 +02:00
parent 16ac53e6af
commit 52df7dc019
Signed by: s3lph
GPG key ID: 8AC98A811E5BEFF5
82 changed files with 516 additions and 719 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
cache/

16
README.md Normal file
View file

@ -0,0 +1,16 @@
# erfamap
Generate a map similar to https://www.ccc.de/regional from Wikidata and the doku.ccc.de Semantic MediaWiki data.
## Usage
For a quick test run, point the script at the cache.example directory:
```
./generate_map.py --cache-directory cache.example
```
## Fonts
The font contained in `style/concertone-regular.ttf` was created by Johan Kallas (johankallas@gmail.com) and is licensed unter the terms of the SIL Open Font License v1.10.

View file

@ -0,0 +1 @@
{"Regensburg": [12.11902809759291, 49.009833549999996], "Erfurt": [11.039392, 50.9828132], "Amsterdam": [4.870297612903226, 52.36339370322581], "Luxemburg": [6.1226317, 49.6207341], "Kiel": [10.135555, 54.3227085], "Alzey": [8.328899881948397, 49.702704049999994], "Augsburg": [10.886817639154547, 48.35789355], "Backnang": [9.4295622, 48.9487308], "Bayreuth": [11.574946989405685, 49.9477097], "Bern": [7.4484931, 46.9564373], "Bielefeld": [8.533127303708476, 52.038277], "Budapest": [19.059225604898185, 47.489209200000005], "Chemnitz": [12.9298883, 50.8166968], "Coburg": [10.9660664, 50.2633598], "Rapperswil-Jona": [8.83370045, 47.2252042], "Cottbus": [14.3221859, 51.768973], "Flensburg": [9.423505460496958, 54.80446775], "Gie\u00dfen": [8.6790748, 50.5820278], "Graz": [15.450608284226139, 47.0655229], "Halle (Saale)": [11.992221563581825, 51.47991935], "Heidelberg": [8.6660724, 49.4183048], "Hildesheim": [9.9467753, 52.1685013], "Hilpoltstein": [11.19428251600182, 49.1892185], "Ingolstadt": [11.381725, 48.7710702], "Innsbruck": [11.3962816, 47.257827], "Iserlohn": [7.6979603, 51.3743032], "Itzehoe": [9.50196591631791, 53.9379972], "Jena": [11.5826767, 50.929203], "Klaus": [9.6460406, 47.304616], "Ludwigsburg": [9.184755297842258, 48.8958537], "Marburg": [8.7783888, 50.8161331], "M\u00fcnster": [7.6386827, 51.9446182], "N\u00fcrnberg": [11.081815196597816, 49.44836835], "Osnabr\u00fcck": [8.047635, 52.2719595], "Potsdam": [13.078557, 52.3894652], "Recklinghausen": [7.1691246, 51.62435], "Rothenburg ob der Tauber": [10.1779991, 49.3783145], "Rotterdam": [4.433639012034096, 51.9099513], "Schwerin": [11.4182505, 53.6010948], "Villingen-Schwenningen": [8.455686041710013, 48.06480535], "Winterthur": [8.7291498, 47.4991723], "Wuppertal": [7.144908700674055, 51.26671315], "L\u00fcbeck": [10.6713232, 53.8687751], "Bonn": [7.0987248, 50.7387823], "Saarbr\u00fccken": [7.0357391, 49.279199], "Aalen": [10.0931765, 48.8362705], "Trier": [6.6338265, 49.7529551], "Aschaffenburg": [9.1358843, 49.9878536], "Offenburg": [7.9458244, 48.4762239], "L\u00f6rrach": [7.6650755, 47.6155335]}

View file

@ -0,0 +1 @@
{"Erlangen": [11.0028028, 49.6000372], "Paderborn": [8.7479251, 51.7171873], "Hamburg": [9.9443486, 53.5583644], "Aachen": [6.091774700480718, 50.7715109], "Basel": [7.6342977, 47.5323068], "Berlin": [13.38283, 52.5217046], "Bremen": [8.7879452, 53.0864586], "K\u00f6ln": [6.912896, 50.9505492], "Darmstadt": [8.6511275, 49.8708409], "Dresden": [13.7288095, 51.0811269], "D\u00fcsseldorf": [6.7996122, 51.2125738], "Frankfurt am Main": [8.6362035, 50.1241797], "Freiburg": [7.8405746483681735, 47.99297755], "G\u00f6ttingen": [9.9446185, 51.5453725], "Hannover": [9.717936908887404, 52.38812135], "Karlsruhe": [8.4073787, 49.0066019], "Mannheim": [8.4886818, 49.4636517], "M\u00fcnchen": [11.5607949, 48.153629], "Salzburg": [13.0561199, 47.7939873], "Stuttgart": [9.1800132, 48.7784485], "Ulm": [9.9910884, 48.4005863], "Wien": [16.35611792351422, 48.209451099999995], "Wiesbaden": [8.2295012, 50.0831784], "Z\u00fcrich": [8.5203159, 47.3869751], "Siegen": [8.0044503, 50.8689203], "Kaiserslautern": [7.762277299724986, 49.44049735], "Essen": [7.024639594585695, 51.43860565], "Dortmund": [7.464966783180763, 51.52768425], "Fulda": [9.6775152, 50.5588931], "W\u00fcrzburg": [9.923678752181619, 49.80223915], "Bamberg": [10.89268892402827, 49.90189135], "Kassel": [9.484939, 51.3183203]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"license":"CC0-1.0","description":{"en":"Aruba","en-gb":"Aruba"},"sources":"Part of the Country Polygons as GeoJSON dataset. Available from Datahub: [https://datahub.io/core/geo-countries https://datahub.io/core/geo-countries], under the [https://opendatacommons.org/licenses/pddl/1.0/ Open Data Commons Public Domain Dedication and License].","zoom":8,"latitude":12.499599017202,"longitude":-69.952428,"data":{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"ADMIN":"Aruba","ISO_A3":"ABW"},"geometry":{"type":"Polygon","coordinates":[[[-69.996937629,12.577582098],[-69.936390754,12.531724351],[-69.924672004,12.519232489],[-69.915760871,12.497015692],[-69.88019772,12.453558661],[-69.876820442,12.427394924],[-69.888091601,12.417669989],[-69.908802864,12.417792059],[-69.930531379,12.42597077],[-69.945139127,12.440375067],[-69.924672004,12.440375067],[-69.924672004,12.447211005],[-69.958566861,12.463202216],[-70.027658658,12.522935289],[-70.04808509,12.53115469],[-70.058094856,12.537176825],[-70.062408007,12.54682038],[-70.060373502,12.556952216],[-70.051096158,12.574042059],[-70.048736132,12.583726304],[-70.052642382,12.600002346],[-70.05964108,12.614243882],[-70.061105924,12.625392971],[-70.048736132,12.632147528],[-70.007150845,12.585516669],[-69.996937629,12.577582098]]]}}]}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"license":"CC0-1.0","description":{"en":"Malta","en-gb":"Malta"},"sources":"Part of the Country Polygons as GeoJSON dataset. Available from Datahub: [https://datahub.io/core/geo-countries https://datahub.io/core/geo-countries], under the [https://opendatacommons.org/licenses/pddl/1.0/ Open Data Commons Public Domain Dedication and License].","zoom":6,"latitude":35.880148964884,"longitude":14.46350097656,"data":{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"ADMIN":"Malta","ISO_A3":"MLT"},"geometry":{"type":"MultiPolygon","coordinates":[[[[14.567149285,35.845607815],[14.561778191,35.829820054],[14.548024936,35.83600495],[14.532481316,35.821966864],[14.527517123,35.813544012],[14.527517123,35.801214911],[14.511485222,35.806463934],[14.507090691,35.8086612],[14.472666863,35.811428127],[14.42448978,35.823675848],[14.38152103,35.842962958],[14.363047722,35.866685289],[14.359873894,35.869696356],[14.345713738,35.875921942],[14.34262129,35.880682684],[14.34262129,35.911688544],[14.338226759,35.946966864],[14.332530144,35.962836005],[14.322032097,35.973130601],[14.33472741,35.982367255],[14.347504102,35.988714911],[14.361501498,35.99241771],[14.377289259,35.993638414],[14.377289259,35.986232815],[14.368174675,35.984849351],[14.3623153,35.982407945],[14.349375847,35.973130601],[14.42847741,35.965725002],[14.445078972,35.960353908],[14.478688998,35.936957098],[14.507823113,35.928534247],[14.512868686,35.920803127],[14.514008009,35.910834052],[14.513926629,35.900824286],[14.519297722,35.899969794],[14.548024936,35.890041408],[14.563161655,35.870021877],[14.567149285,35.845607815]]],[[[14.277598504,36.016669012],[14.260020379,36.013495184],[14.241465691,36.014634507],[14.220876498,36.019191799],[14.201182488,36.026068427],[14.184906446,36.034613348],[14.187998894,36.045396226],[14.184336785,36.056301174],[14.183604363,36.064764716],[14.195485873,36.068101304],[14.25660241,36.075588283],[14.282562696,36.067857164],[14.314789259,36.051092841],[14.334239129,36.034369208],[14.322032097,36.027167059],[14.304453972,36.026597398],[14.277598504,36.016669012]]]]}}]}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"license":"CC0-1.0","description":{"en":"Liechtenstein","en-gb":"Liechtenstein"},"sources":"Part of the Country Polygons as GeoJSON dataset. Available from Datahub: [https://datahub.io/core/geo-countries https://datahub.io/core/geo-countries], under the [https://opendatacommons.org/licenses/pddl/1.0/ Open Data Commons Public Domain Dedication and License].","zoom":8,"latitude":47.143963438088,"longitude":9.5553588867188,"data":{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"ADMIN":"Liechtenstein","ISO_A3":"LIE"},"geometry":{"type":"Polygon","coordinates":[[[9.58120284,47.056870423],[9.5606356200001,47.052400412],[9.4995540770001,47.059350891],[9.4770231530001,47.063898417],[9.4758862710001,47.073226014],[9.4875651450001,47.083948874],[9.5028613680001,47.094697572],[9.512369832,47.108030091],[9.5118530680001,47.129372457],[9.5034814860001,47.145392151],[9.4926294350001,47.159809876],[9.4849813230001,47.176346334],[9.487358439,47.210013529],[9.5046183680001,47.243732402],[9.5211548250001,47.262801005],[9.5302498780001,47.253654277],[9.5470963950001,47.24303477],[9.5403784580001,47.229107972],[9.5448226320001,47.220322978],[9.5543310950001,47.211615499],[9.5629093830001,47.197740377],[9.5618758540001,47.190609029],[9.5519539790001,47.175571188],[9.5520573320001,47.166889547],[9.5818229570001,47.154900615],[9.6055941160001,47.132266337],[9.6157226970001,47.106764018],[9.6087980550001,47.080770773],[9.58120284,47.056870423]]]}}]}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"license":"CC0-1.0","description":{"en":"Liechtenstein","en-gb":"Liechtenstein"},"sources":"Part of the Country Polygons as GeoJSON dataset. Available from Datahub: [https://datahub.io/core/geo-countries https://datahub.io/core/geo-countries], under the [https://opendatacommons.org/licenses/pddl/1.0/ Open Data Commons Public Domain Dedication and License].","zoom":8,"latitude":47.143963438088,"longitude":9.5553588867188,"data":{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"ADMIN":"Liechtenstein","ISO_A3":"LIE"},"geometry":{"type":"Polygon","coordinates":[[[9.58120284,47.056870423],[9.5606356200001,47.052400412],[9.4995540770001,47.059350891],[9.4770231530001,47.063898417],[9.4758862710001,47.073226014],[9.4875651450001,47.083948874],[9.5028613680001,47.094697572],[9.512369832,47.108030091],[9.5118530680001,47.129372457],[9.5034814860001,47.145392151],[9.4926294350001,47.159809876],[9.4849813230001,47.176346334],[9.487358439,47.210013529],[9.5046183680001,47.243732402],[9.5211548250001,47.262801005],[9.5302498780001,47.253654277],[9.5470963950001,47.24303477],[9.5403784580001,47.229107972],[9.5448226320001,47.220322978],[9.5543310950001,47.211615499],[9.5629093830001,47.197740377],[9.5618758540001,47.190609029],[9.5519539790001,47.175571188],[9.5520573320001,47.166889547],[9.5818229570001,47.154900615],[9.6055941160001,47.132266337],[9.6157226970001,47.106764018],[9.6087980550001,47.080770773],[9.58120284,47.056870423]]]}}]}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"license":"CC0-1.0","description":{"en":"Hamburg","en-gb":"Hamburg"},"sources":"Created from the Natural Earth Admin1 Polygons dataset. Available from Datahub:[https://datahub.io/core/geo-ne-admin1 https://datahub.io/core/geo-ne-admin1], under the [https://opendatacommons.org/licenses/pddl/1.0/ Open Data Commons Public Domain Dedication and License].","zoom":8,"latitude":53.45493,"longitude":10.31263,"data":{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"stroke":"#555555","stroke-width":2,"stroke-opacity":1,"fill":"#555555","fill-opacity":0.5},"geometry":{"type":"MultiPolygon","coordinates":[[[[10.31263,53.45493],[10.31341,53.45524],[10.31496,53.4556],[10.31883,53.45586],[10.32069,53.45565],[10.32317,53.45043],[10.32798,53.43266],[10.28535,53.43266],[10.27599,53.4308],[10.26607,53.42708],[10.24375,53.4094],[10.24158,53.40661],[10.18654,53.40439],[10.16985,53.40661],[10.15357,53.41364],[10.14308,53.42268],[10.1314,53.43059],[10.1112,53.4339],[10.0991,53.43948],[10.08314,53.4509],[10.07601,53.45043],[10.03875,53.44309],[10.03239,53.44098],[10.02945,53.43839],[10.0296,53.43664],[10.03007,53.43452],[10.03069,53.43307],[10.03084,53.43116],[10.03038,53.42919],[10.02789,53.42749],[10.01131,53.42149],[9.98537,53.41506],[9.97529,53.41405],[9.96847,53.41447],[9.95002,53.42201],[9.94501,53.42284],[9.9388,53.42188],[9.91896,53.4156],[9.91291,53.4156],[9.90935,53.41808],[9.90873,53.42155],[9.90811,53.4308],[9.90547,53.44123],[9.90005,53.44661],[9.89571,53.44865],[9.89085,53.44904],[9.88279,53.44723],[9.87752,53.44477],[9.86511,53.43341],[9.86124,53.43224],[9.85721,53.43286],[9.82444,53.45141],[9.81669,53.45694],[9.81065,53.46237],[9.80476,53.46919],[9.80073,53.47524],[9.79607,53.48428],[9.79375,53.48779],[9.79142,53.48981],[9.78522,53.49245],[9.78243,53.49431],[9.78073,53.49658],[9.77933,53.50009],[9.77726,53.50232],[9.76843,53.50816],[9.76471,53.54376],[9.76397,53.54768],[9.76401,53.54767],[9.81902,53.54035],[9.83204,53.54361],[9.81959,53.55663],[9.78207,53.56973],[9.74081,53.57257],[9.74088,53.57275],[9.74889,53.59197],[9.7506,53.59802],[9.75168,53.60469],[9.75277,53.60789],[9.75401,53.61004],[9.75618,53.61182],[9.76021,53.6142],[9.76331,53.61657],[9.76502,53.61887],[9.76657,53.62629],[9.76843,53.62918],[9.77168,53.6297],[9.77649,53.62939],[9.78817,53.62758],[9.79654,53.62463],[9.79871,53.62324],[9.80693,53.61988],[9.81003,53.61786],[9.81049,53.61507],[9.80755,53.6065],[9.80646,53.6019],[9.80615,53.59905],[9.80693,53.59714],[9.80801,53.59611],[9.8091,53.59528],[9.81049,53.59461],[9.81251,53.59389],[9.81607,53.59311],[9.82863,53.59725],[9.92733,53.64567],[9.95281,53.65037],[9.9804,53.64673],[9.98428,53.64688],[9.98986,53.64856],[9.99405,53.65143],[10.00371,53.6668],[10.00821,53.67068],[10.01363,53.673],[10.02588,53.67551],[10.03286,53.67628],[10.03906,53.67626],[10.05223,53.67486],[10.06019,53.67796],[10.06515,53.68081],[10.07818,53.7129],[10.08205,53.71636],[10.08639,53.71889],[10.08872,53.71972],[10.09027,53.72008],[10.0929,53.72018],[10.14225,53.71331],[10.14799,53.71367],[10.15357,53.71564],[10.17109,53.7253],[10.17688,53.72721],[10.18199,53.72778],[10.18742,53.72752],[10.19192,53.72657],[10.19378,53.72509],[10.19486,53.72254],[10.19285,53.7177],[10.19114,53.71517],[10.17982,53.70561],[10.16985,53.6936],[10.16582,53.68737],[10.16473,53.68453],[10.16597,53.6821],[10.16923,53.67864],[10.20478,53.66282],[10.2054,53.66236],[10.20649,53.65698],[10.2068,53.65047],[10.20912,53.64479],[10.21455,53.64184],[10.21889,53.64024],[10.22153,53.63781],[10.22277,53.63365],[10.22122,53.62618],[10.21998,53.62288],[10.21672,53.61771],[10.21533,53.6142],[10.21331,53.60169],[10.21238,53.58815],[10.2116,53.58557],[10.21067,53.58435],[10.20866,53.58303],[10.20602,53.58187],[10.19595,53.57921],[10.17812,53.57652],[10.17548,53.57544],[10.17316,53.57378],[10.17285,53.57053],[10.17409,53.56598],[10.18075,53.54963],[10.1823,53.54097],[10.18215,53.53405],[10.18323,53.52896],[10.18664,53.52531],[10.1975,53.52175],[10.21393,53.51883],[10.22277,53.51301],[10.24762,53.48878],[10.26979,53.48149],[10.27785,53.47751],[10.31263,53.45493]]]]}}]}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"license":"CC0-1.0","description":{"en":"Saarland","en-gb":"Saarland"},"sources":"Created from the Natural Earth Admin1 Polygons dataset. Available from Datahub:[https://datahub.io/core/geo-ne-admin1 https://datahub.io/core/geo-ne-admin1], under the [https://opendatacommons.org/licenses/pddl/1.0/ Open Data Commons Public Domain Dedication and License].","zoom":8,"latitude":49.56354,"longitude":7.21913,"data":{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"stroke":"#555555","stroke-width":2,"stroke-opacity":1,"fill":"#555555","fill-opacity":0.5},"geometry":{"type":"MultiPolygon","coordinates":[[[[7.21913,49.56354],[7.23788,49.56617],[7.24719,49.56834],[7.25592,49.57232],[7.26181,49.57599],[7.26755,49.5785],[7.27065,49.5778],[7.27328,49.57672],[7.27344,49.56168],[7.27948,49.54891],[7.29096,49.53434],[7.29235,49.52519],[7.29235,49.51191],[7.29359,49.50711],[7.29576,49.50427],[7.29979,49.50396],[7.30382,49.50323],[7.30584,49.50251],[7.30723,49.50044],[7.30816,49.49726],[7.30909,49.49026],[7.30847,49.4854],[7.30537,49.48204],[7.29514,49.47879],[7.28755,49.47548],[7.27917,49.47078],[7.25979,49.4545],[7.25478,49.45166],[7.25091,49.45099],[7.2461,49.44613],[7.2523,49.43099],[7.27638,49.41213],[7.29344,49.39859],[7.29576,49.39425],[7.29499,49.39342],[7.29452,49.39254],[7.29344,49.39146],[7.29142,49.38882],[7.30119,49.38484],[7.35116,49.37931],[7.3754,49.37337],[7.38516,49.36967],[7.39059,49.36071],[7.38485,49.35384],[7.38423,49.35166],[7.38346,49.34815],[7.38687,49.34386],[7.38795,49.3404],[7.38733,49.33683],[7.38253,49.33084],[7.37881,49.32743],[7.37462,49.32464],[7.3631,49.31898],[7.35845,49.31567],[7.35674,49.31273],[7.35426,49.3018],[7.35271,49.29771],[7.34961,49.29472],[7.34279,49.291],[7.34,49.28883],[7.33736,49.28609],[7.33581,49.28299],[7.33473,49.27921],[7.33333,49.27182],[7.32992,49.26418],[7.32325,49.25674],[7.32062,49.25451],[7.3186,49.25299],[7.31674,49.25193],[7.31457,49.2511],[7.31194,49.25043],[7.30739,49.25007],[7.30584,49.25017],[7.30491,49.25033],[7.30398,49.25053],[7.30243,49.25079],[7.3001,49.25095],[7.29654,49.25048],[7.29576,49.2502],[7.29421,49.24842],[7.28817,49.2402],[7.28662,49.23555],[7.28693,49.23079],[7.29034,49.22278],[7.29235,49.217],[7.2939,49.20726],[7.29468,49.20516],[7.33023,49.1794],[7.34914,49.16868],[7.36919,49.16904],[7.37126,49.16661],[7.37163,49.16617],[7.36759,49.16537],[7.35095,49.15917],[7.3463,49.15421],[7.33907,49.13969],[7.334,49.13416],[7.32646,49.13183],[7.30599,49.13013],[7.2971,49.12811],[7.2908,49.1231],[7.28026,49.10899],[7.27437,49.10501],[7.26806,49.10563],[7.24119,49.1139],[7.15499,49.11442],[7.13928,49.11793],[7.12358,49.12331],[7.08533,49.14155],[7.07986,49.14201],[7.07076,49.13612],[7.07159,49.13059],[7.07479,49.12537],[7.07324,49.12031],[7.04255,49.1076],[7.02053,49.11897],[7.00896,49.14641],[7.00947,49.1817],[6.98787,49.18243],[6.92545,49.20558],[6.91439,49.20666],[6.88524,49.2048],[6.84349,49.21105],[6.83346,49.20945],[6.82768,49.19555],[6.83522,49.17938],[6.83904,49.16279],[6.82178,49.14775],[6.80907,49.14584],[6.77011,49.1553],[6.72556,49.15555],[6.71378,49.15886],[6.70097,49.17287],[6.70376,49.18511],[6.70882,49.19777],[6.70252,49.21364],[6.69848,49.21359],[6.68505,49.2078],[6.6806,49.20749],[6.67792,49.21105],[6.67595,49.21891],[6.67482,49.22113],[6.67161,49.22532],[6.66376,49.24387],[6.65952,49.24676],[6.64567,49.25002],[6.6403,49.25276],[6.63916,49.25679],[6.64123,49.26692],[6.63968,49.27115],[6.57317,49.31312],[6.55643,49.3264],[6.55012,49.33802],[6.5556,49.34717],[6.57338,49.34774],[6.57772,49.3558],[6.57431,49.36179],[6.52573,49.39549],[6.51881,49.40215],[6.52077,49.40257],[6.52056,49.40815],[6.51788,49.41642],[6.51167,49.42474],[6.49452,49.43554],[6.40274,49.46546],[6.39168,49.4667],[6.38352,49.46463],[6.3645,49.4561],[6.35385,49.45476],[6.34531,49.45535],[6.34538,49.46272],[6.34228,49.493],[6.34881,49.52529],[6.35649,49.52592],[6.48997,49.53382],[6.60511,49.53021],[6.66981,49.54938],[6.69352,49.54793],[6.6973,49.54809],[6.71233,49.55031],[6.85972,49.59449],[6.9364,49.62715],[6.94214,49.62793],[6.94881,49.62746],[6.96214,49.62359],[6.97113,49.61868],[6.97862,49.61958],[6.99878,49.62948],[7.06022,49.60751],[7.06518,49.60493],[7.072,49.60354],[7.13283,49.60201],[7.13794,49.6009],[7.15954,49.5871],[7.19659,49.57005],[7.19959,49.56731],[7.20223,49.56545],[7.20843,49.56437],[7.21913,49.56354]]]]}}]}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"license":"CC0-1.0","description":{"en":"Bremen","en-gb":"Bremen"},"sources":"Created from the Natural Earth Admin1 Polygons dataset. Available from Datahub:[https://datahub.io/core/geo-ne-admin1 https://datahub.io/core/geo-ne-admin1], under the [https://opendatacommons.org/licenses/pddl/1.0/ Open Data Commons Public Domain Dedication and License].","zoom":8,"latitude":53.21257,"longitude":8.56059,"data":{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"stroke":"#555555","stroke-width":2,"stroke-opacity":1,"fill":"#555555","fill-opacity":0.5},"geometry":{"type":"MultiPolygon","coordinates":[[[[8.56059,53.21257],[8.56447,53.21179],[8.56633,53.21184],[8.58509,53.21541],[8.5902,53.21536],[8.61785,53.20484],[8.67681,53.19037],[8.6903,53.18843],[8.70115,53.18911],[8.70363,53.18952],[8.70472,53.18993],[8.70658,53.19112],[8.71046,53.19177],[8.73314,53.19267],[8.74554,53.19161],[8.74756,53.19076],[8.74895,53.1896],[8.74988,53.18833],[8.75019,53.18688],[8.74988,53.18549],[8.74926,53.18389],[8.75144,53.18146],[8.75655,53.17872],[8.77862,53.17027],[8.78373,53.169],[8.81164,53.16957],[8.82389,53.16807],[8.82575,53.16714],[8.84068,53.15794],[8.86052,53.14926],[8.89499,53.13885],[8.90398,53.13761],[8.90941,53.13851],[8.91236,53.13934],[8.91437,53.14063],[8.91577,53.14234],[8.9167,53.14397],[8.91778,53.14694],[8.91933,53.14978],[8.92398,53.1566],[8.92631,53.15934],[8.92848,53.16112],[8.93297,53.16298],[8.93623,53.1628],[8.93938,53.16182],[8.94946,53.15603],[8.97302,53.13955],[8.97535,53.13702],[8.97519,53.13469],[8.97209,53.13247],[8.96047,53.12606],[8.9583,53.1233],[8.95783,53.12193],[8.96155,53.11583],[8.98605,53.10007],[8.98822,53.09717],[8.98992,53.09361],[8.98822,53.09159],[8.9814,53.08593],[8.97907,53.08167],[8.97674,53.07562],[8.97535,53.06188],[8.97411,53.05637],[8.97271,53.05258],[8.95768,53.0426],[8.9229,53.02744],[8.91453,53.02462],[8.86595,53.04751],[8.86564,53.04687],[8.86549,53.04539],[8.86595,53.04265],[8.86595,53.03973],[8.86316,53.03728],[8.85804,53.03568],[8.84409,53.0332],[8.83448,53.03353],[8.82466,53.03501],[8.77707,53.05335],[8.7718,53.05464],[8.76833,53.05464],[8.75857,53.05211],[8.7195,53.04793],[8.70487,53.04955],[8.70394,53.05048],[8.70239,53.05113],[8.70255,53.0517],[8.7027,53.05273],[8.70239,53.05436],[8.7027,53.07986],[8.70177,53.08343],[8.6996,53.08586],[8.69294,53.0881],[8.65382,53.11572],[8.63801,53.1303],[8.63583,53.13391],[8.63382,53.13898],[8.63335,53.14141],[8.63149,53.15957],[8.61723,53.17686],[8.61103,53.18006],[8.58922,53.17898],[8.57806,53.17978],[8.55444,53.1896],[8.513,53.19975],[8.50458,53.21107],[8.50137,53.21706],[8.49605,53.22404],[8.51868,53.22611],[8.52954,53.22466],[8.56059,53.21257]]],[[[8.56245,53.60626],[8.58431,53.60394],[8.62529,53.605],[8.63087,53.60396],[8.6318,53.602],[8.62824,53.59936],[8.62529,53.59681],[8.62297,53.59399],[8.62173,53.59148],[8.62018,53.58761],[8.62111,53.58226],[8.62359,53.57709],[8.63149,53.56753],[8.63521,53.55988],[8.64188,53.53932],[8.64266,53.5319],[8.64188,53.52531],[8.64064,53.51989],[8.63909,53.51666],[8.62359,53.50221],[8.60281,53.49048],[8.60106,53.49002],[8.59951,53.48973],[8.59749,53.49059],[8.59501,53.49219],[8.58834,53.50017],[8.58788,53.50033],[8.58741,53.50035],[8.58555,53.50033],[8.57827,53.49924],[8.573,53.50185],[8.5609,53.5157],[8.5572,53.51994],[8.56577,53.5467],[8.5608,53.5517],[8.53517,53.59077],[8.52752,53.59663],[8.5171,53.61017],[8.51304,53.6171],[8.51341,53.61701],[8.56245,53.60626]]]]}}]}}

View file

@ -0,0 +1 @@
{"license":"CC0-1.0","description":{"en":"Berlin","en-gb":"Berlin"},"sources":"Created from the Natural Earth Admin1 Polygons dataset. Available from Datahub:[https://datahub.io/core/geo-ne-admin1 https://datahub.io/core/geo-ne-admin1], under the [https://opendatacommons.org/licenses/pddl/1.0/ Open Data Commons Public Domain Dedication and License].","zoom":8,"latitude":52.48703,"longitude":13.62633,"data":{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"stroke":"#555555","stroke-width":2,"stroke-opacity":1,"fill":"#555555","fill-opacity":0.5},"geometry":{"type":"MultiPolygon","coordinates":[[[[13.62633,52.48703],[13.66452,52.48977],[13.67754,52.48553],[13.6963,52.4708],[13.7362,52.4598],[13.74255,52.45636],[13.75092,52.45029],[13.75092,52.44688],[13.74953,52.44486],[13.74302,52.4415],[13.73976,52.43949],[13.73852,52.43631],[13.73806,52.43313],[13.73852,52.42665],[13.73666,52.42277],[13.73434,52.41964],[13.71604,52.41039],[13.71232,52.40791],[13.7086,52.40461],[13.70452,52.39789],[13.70452,52.39197],[13.70689,52.38316],[13.70669,52.37804],[13.70534,52.37427],[13.70348,52.37195],[13.70142,52.37001],[13.70002,52.36905],[13.69801,52.36812],[13.69615,52.36755],[13.67336,52.36285],[13.67119,52.3621],[13.667,52.35965],[13.65398,52.34234],[13.64447,52.34203],[13.64354,52.34352],[13.64261,52.34479],[13.64261,52.34748],[13.64121,52.36073],[13.63734,52.37598],[13.63238,52.38187],[13.62649,52.3844],[13.61501,52.38347],[13.6099,52.38378],[13.58473,52.3889],[13.56582,52.39567],[13.52809,52.40492],[13.51972,52.40486],[13.51445,52.40368],[13.51197,52.40166],[13.50918,52.3999],[13.50608,52.39879],[13.50313,52.39815],[13.49492,52.39794],[13.48438,52.39866],[13.47952,52.40047],[13.4758,52.40365],[13.47332,52.40752],[13.46805,52.4137],[13.46309,52.4159],[13.45812,52.41675],[13.44898,52.4159],[13.43363,52.4123],[13.43006,52.41109],[13.42712,52.40946],[13.42495,52.40734],[13.42402,52.40445],[13.42412,52.40161],[13.42474,52.39887],[13.42691,52.3937],[13.42753,52.3907],[13.42753,52.38812],[13.42567,52.38109],[13.39405,52.38122],[13.37575,52.38657],[13.37151,52.38952],[13.36552,52.39546],[13.35462,52.40192],[13.34935,52.4059],[13.34578,52.4074],[13.33663,52.40595],[13.31271,52.4005],[13.30919,52.40109],[13.3048,52.40228],[13.29922,52.41029],[13.29596,52.41401],[13.29426,52.4151],[13.2924,52.41484],[13.29116,52.41411],[13.28573,52.41202],[13.25824,52.40551],[13.25219,52.40476],[13.24816,52.40507],[13.23964,52.41106],[13.22677,52.41592],[13.21964,52.41711],[13.21406,52.41706],[13.17261,52.40517],[13.16951,52.40368],[13.16657,52.40156],[13.16331,52.39815],[13.15928,52.39711],[13.15649,52.39685],[13.12171,52.40114],[13.11799,52.40202],[13.11194,52.40399],[13.09489,52.41199],[13.09272,52.41326],[13.0921,52.4137],[13.09179,52.41422],[13.09164,52.41525],[13.0921,52.41675],[13.09365,52.41871],[13.10311,52.42584],[13.10419,52.42683],[13.10543,52.42832],[13.10528,52.4299],[13.10574,52.43228],[13.10745,52.43453],[13.10977,52.43654],[13.11241,52.4383],[13.11675,52.44047],[13.11691,52.4415],[13.11179,52.44737],[13.11055,52.44987],[13.10915,52.4554],[13.10915,52.45858],[13.11055,52.46501],[13.11287,52.47111],[13.11442,52.4738],[13.1166,52.47597],[13.11954,52.4777],[13.12652,52.48],[13.12962,52.4816],[13.1568,52.50103],[13.15649,52.50419],[13.15664,52.50695],[13.14093,52.52429],[13.14331,52.55193],[13.14781,52.56653],[13.14843,52.57571],[13.14745,52.58098],[13.14486,52.58516],[13.14548,52.588],[13.14683,52.59051],[13.14889,52.59286],[13.1523,52.59793],[13.15478,52.59955],[13.15742,52.6003],[13.16486,52.59819],[13.20755,52.58676],[13.21685,52.58744],[13.21685,52.58945],[13.21607,52.59144],[13.21499,52.59302],[13.21189,52.59622],[13.21034,52.59839],[13.20925,52.60087],[13.20925,52.60382],[13.21003,52.60676],[13.21127,52.60961],[13.22072,52.62599],[13.22351,52.62725],[13.25266,52.62989],[13.26155,52.63307],[13.27255,52.63916],[13.27674,52.64392],[13.28,52.64841],[13.28248,52.65397],[13.28589,52.65606],[13.28914,52.65712],[13.3003,52.65694],[13.30279,52.65619],[13.30186,52.6548],[13.3003,52.65322],[13.29968,52.65131],[13.30263,52.64715],[13.30372,52.64454],[13.3031,52.63529],[13.30449,52.63307],[13.30697,52.63115],[13.31023,52.62966],[13.32656,52.62521],[13.36423,52.62361],[13.3742,52.6249],[13.37978,52.62627],[13.38211,52.62831],[13.38335,52.63084],[13.38877,52.63689],[13.41203,52.64872],[13.4313,52.64095],[13.4341,52.64095],[13.43844,52.64159],[13.44014,52.64438],[13.44247,52.64728],[13.4451,52.64971],[13.46851,52.66332],[13.47378,52.66356],[13.47673,52.66162],[13.47942,52.65777],[13.48355,52.65415],[13.49972,52.64526],[13.50127,52.64351],[13.50236,52.64087],[13.50251,52.63854],[13.50081,52.6294],[13.49926,52.62428],[13.49926,52.61942],[13.50003,52.61389],[13.5005,52.61307],[13.50329,52.61017],[13.56907,52.56341],[13.59093,52.55338],[13.60308,52.55051],[13.6116,52.54524],[13.62649,52.54113],[13.64757,52.53927],[13.6485,52.53163],[13.62509,52.49809],[13.62339,52.49065],[13.62633,52.48703]]]]}}]}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

490
generate_map.py Normal file → Executable file
View file

@ -2,24 +2,83 @@
import os
import urllib.request
import urllib.parse
import json
import base64
import sys
import argparse
import shutil
from lxml import etree
import pyproj
from SPARQLWrapper import SPARQLWrapper, JSON
from geopy import Nominatim
import tqdm
import cairosvg
from PIL import Image, ImageFont
from PIL.ImageDraw import ImageDraw
BOUNDING_BOX = [(4.27775, 46.7482713), (19.2403594, 54.9833021)]
USER_AGENT = 'Erfamap/v0.1 (https://git.kabelsalat.ch/s3lph/erfamap)'
class CachePaths:
def __init__(self, path: str):
if os.path.exists(path) and not os.path.isdir(path):
raise AttributeError(f'Path exists but is not a directory: {path}')
os.makedirs(path, exist_ok=True)
self.shapes_states = os.path.join(path, 'shapes_states')
self.shapes_countries = os.path.join(path, 'shapes_countries')
self.shapes_filtered = os.path.join(path, 'shapes_filtered')
self.erfa_info = os.path.join(path, 'erfa-info.json')
self.chaostreff_info = os.path.join(path, 'chaostreff-info.json')
#BOUNDING_BOX = [(4.27775, 46.7482713), (19.2403594, 54.9833021)]
# --bbox 4.27775 46.7482713 19.2403594 54.9833021
ERFA_URL = 'https://doku.ccc.de/Spezial:Semantische_Suche/format%3Djson/limit%3D50/link%3Dall/headers%3Dshow/searchlabel%3DJSON/class%3Dsortable-20wikitable-20smwtable/sort%3D/order%3Dasc/offset%3D0/-5B-5BKategorie:Erfa-2DKreise-5D-5D-20-5B-5BChaostreff-2DActive::wahr-5D-5D/-3FChaostreff-2DCity/-3FChaostreff-2DPhysical-2DAddress/-3FChaostreff-2DPhysical-2DHousenumber/-3FChaostreff-2DPhysical-2DPostcode/-3FChaostreff-2DPhysical-2DCity/-3FChaostreff-2DCountry/mainlabel%3D/prettyprint%3Dtrue/unescape%3Dtrue'
CHAOSTREFF_URL = 'https://doku.ccc.de/Spezial:Semantische_Suche/format%3Djson/limit%3D50/link%3Dall/headers%3Dshow/searchlabel%3DJSON/class%3Dsortable-20wikitable-20smwtable/sort%3D/order%3Dasc/offset%3D0/-5B-5BKategorie:Chaostreffs-5D-5D-20-5B-5BChaostreff-2DActive::wahr-5D-5D/-3FChaostreff-2DCity/-3FChaostreff-2DPhysical-2DAddress/-3FChaostreff-2DPhysical-2DHousenumber/-3FChaostreff-2DPhysical-2DPostcode/-3FChaostreff-2DPhysical-2DCity/-3FChaostreff-2DCountry/mainlabel%3D/prettyprint%3Dtrue/unescape%3Dtrue'
def fetch_wikidata_states(target='shapes_states'):
sparql = SPARQLWrapper('https://query.wikidata.org/sparql')
sparql.setQuery('''
def sparql_query(query):
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': USER_AGENT,
'Accept': 'application/sparql-results+json',
}
body = urllib.parse.urlencode({'query': query}).encode()
req = urllib.request.Request('https://query.wikidata.org/sparql', headers=headers, data=body)
with urllib.request.urlopen(req) as resp:
resultset = json.load(resp)
results = {}
for r in resultset.get('results', {}).get('bindings'):
basename = None
url = None
for k, v in r.items():
if k == 'item':
basename = os.path.basename(v['value'])
elif k == 'map':
url = v['value']
results[basename] = url
return results
def fetch_geoshapes(target, shape_urls):
os.makedirs(target, exist_ok=True)
for item, url in tqdm.tqdm(shape_urls.items()):
try:
with urllib.request.urlopen(url) as resp:
with open(os.path.join(target, item + '.json'), 'wb') as f:
f.write(resp.read())
except urllib.error.HTTPError as e:
print(e)
def fetch_wikidata_states(target):
shape_urls = sparql_query('''
PREFIX wd: <http://www.wikidata.org/entity/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
@ -30,20 +89,12 @@ SELECT DISTINCT ?item ?map WHERE {
wdt:P3896 ?map
}
''')
sparql.setReturnFormat(JSON)
results = sparql.query().convert()
print('Retrieving state border shapes')
fetch_geoshapes(target, shape_urls)
shape_urls = {result['item']['value'].split('/')[-1]: result['map']['value'] for result in results["results"]["bindings"]}
os.makedirs(target, exist_ok=True)
for item, url in shape_urls.items():
with urllib.request.urlopen(url) as resp:
with open(os.path.join(target, item + '.json'), 'wb') as f:
f.write(resp.read())
def fetch_wikidata_countries(target='shapes_countries'):
sparql = SPARQLWrapper('https://query.wikidata.org/sparql')
sparql.setQuery('''
def fetch_wikidata_countries(target):
shape_urls = sparql_query('''
PREFIX wd: <http://www.wikidata.org/entity/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
@ -60,25 +111,15 @@ SELECT DISTINCT ?item ?map WHERE {
FILTER (?euroclass = wd:Q46 || ?euroclass = wd:Q8932).
}
''')
sparql.setReturnFormat(JSON)
results = sparql.query().convert()
print('Retrieving country border shapes')
fetch_geoshapes(target, shape_urls)
shape_urls = {result['item']['value'].split('/')[-1]: result['map']['value'] for result in results["results"]["bindings"]}
os.makedirs(target, exist_ok=True)
for item, url in shape_urls.items():
try:
with urllib.request.urlopen(url) as resp:
with open(os.path.join(target, item + '.json'), 'wb') as f:
f.write(resp.read())
except BaseException as e:
print(e)
print(url)
def filter_boundingbox(source='shapes_countries', target='shapes_filtered'):
def filter_boundingbox(source, target, bbox):
files = os.listdir(source)
os.makedirs(target, exist_ok=True)
for f in files:
print('Filtering countries outside the bounding box')
for f in tqdm.tqdm(files):
if not f.endswith('.json') or 'Q183.json' in f:
continue
path = os.path.join(source, f)
@ -91,8 +132,8 @@ def filter_boundingbox(source='shapes_countries', target='shapes_filtered'):
geo['coordinates'] = [geo['coordinates']]
for poly in geo['coordinates']:
for point in poly[0]:
if point[0] >= BOUNDING_BOX[0][0] and point[1] >= BOUNDING_BOX[0][1] \
and point[0] <= BOUNDING_BOX[1][0] and point[1] <= BOUNDING_BOX[1][1]:
if point[0] >= bbox[0][0] and point[1] >= bbox[0][1] \
and point[0] <= bbox[1][0] and point[1] <= bbox[1][1]:
keep = True
break
if keep:
@ -102,18 +143,38 @@ def filter_boundingbox(source='shapes_countries', target='shapes_filtered'):
sf.write(shapedata)
def address_lookup(erfa):
locator = Nominatim(user_agent='foobar')
city = erfa['printouts']['Chaostreff-City'][0]
country = erfa['printouts']['Chaostreff-Country'][0]
address = f'{city}, {country}'
response = locator.geocode(address)
if response is None:
return None
return response.longitude, response.latitude
def address_lookup(name, erfa):
locator = Nominatim(user_agent=USER_AGENT)
number = erfa['Chaostreff-Physical-Housenumber']
street = erfa['Chaostreff-Physical-Address']
zipcode = erfa['Chaostreff-Physical-Postcode']
acity = erfa['Chaostreff-Physical-City']
city = erfa['Chaostreff-City'][0]
country = erfa['Chaostreff-Country'][0]
formats = [
# Muttenz, Schweiz
f'{city}, {country}'
]
if zipcode and acity:
# 4132 Muttenz, Schweiz
formats.insert(0, f'{zipcode[0]} {acity[0]}, {country}')
if zipcode and acity and number and street:
# Birsfelderstrasse 6, 4132 Muttenz, Schweiz
formats.insert(0, f'{street[0]} {number[0]}, {zipcode[0]} {acity[0]}, {country}')
for fmt in formats:
response = locator.geocode(fmt)
if response is not None:
return response.longitude, response.latitude
print(f'No location found for {name}, tried the following address formats:')
for fmt in formats:
print(f' {fmt}')
return None
def fetch_erfas(target='erfa-info.json', url=ERFA_URL, bbox=None):
def fetch_erfas(target, url):
userpw = os.getenv('DOKU_CCC_DE_BASICAUTH')
if userpw is None:
print('Please set environment variable DOKU_CCC_DE_BASICAUTH=username:password')
@ -123,44 +184,189 @@ def fetch_erfas(target='erfa-info.json', url=ERFA_URL, bbox=None):
req = urllib.request.Request(url, headers={'Authorization': f'Basic {auth}'})
with urllib.request.urlopen(req) as resp:
erfadata = json.loads(resp.read().decode())
for name, erfa in erfadata['results'].items():
location = address_lookup(erfa)
print('Looking up addresses')
for name, erfa in tqdm.tqdm(erfadata['results'].items()):
location = address_lookup(name, erfa['printouts'])
if location is None:
print(f'WARNING: No location for {name}')
city = erfa['printouts']['Chaostreff-City'][0]
erfas[city] = location
if len(bbox) == 0:
bbox.append(location)
bbox.append(location)
else:
bbox[0] = (min(bbox[0][0], location[0]), min(bbox[0][1], location[1]))
bbox[1] = (max(bbox[1][0], location[0]), max(bbox[1][1], location[1]))
with open(target, 'w') as f:
json.dump(erfas, f)
def fetch_chaostreffs(target='chaostreff-info.json', bbox=None):
fetch_erfas(target=target, url=CHAOSTREFF_URL, bbox=bbox)
def compute_bbox(ns):
if ns.bbox is not None:
return [
(min(ns.bbox[0], ns.bbox[2]), min(ns.bbox[1], ns.bbox[3])),
(max(ns.bbox[0], ns.bbox[2]), max(ns.bbox[1], ns.bbox[3]))
]
print('Computing map bounding box')
bounds = []
for path in tqdm.tqdm([ns.cache_directory.erfa_info, ns.cache_directory.chaostreff_info]):
with open(path, 'r') as f:
erfadata = json.load(f)
for lon, lat in erfadata.values():
if len(bounds) == 0:
bounds.append(lon)
bounds.append(lat)
bounds.append(lon)
bounds.append(lat)
else:
bounds[0] = min(bounds[0], lon)
bounds[1] = min(bounds[1], lat)
bounds[2] = max(bounds[2], lon)
bounds[3] = max(bounds[3], lat)
return [
(bounds[0] - ns.bbox_margin, bounds[1] - ns.bbox_margin),
(bounds[2] + ns.bbox_margin, bounds[3] + ns.bbox_margin)
]
def create_svg(source_states='shapes_states', source_countries='shapes_filtered', source_erfa='erfa-info.json', source_ct='chaostreff-info.json'):
transformer = pyproj.Transformer.from_crs('epsg:4326', 'epsg:4258')
scalex = 130
scaley = 200
blt = transformer.transform(*BOUNDING_BOX[0])
trt = transformer.transform(*BOUNDING_BOX[1])
jtm_bounding_box = [
class BoundingBox:
def __init__(self, left, top, right, bottom):
self.left = left
self.top = top
self.right = right
self.bottom = bottom
def __contains__(self, other):
if isinstance(other, BoundingBox):
if other.right < self.left or other.left > self.right:
return False
if other.bottom < self.top or other.top > self.bottom:
return False
return True
elif isinstance(other, tuple) or isinstance(other, list):
if len(other) != 2:
raise TypeError()
if self.left > other[0] or self.right < other[0]:
return False
if self.top > other[1] or self.bottom < other[1]:
return False
return True
raise TypeError()
@property
def width(self):
return self.right - self.left
@property
def height(self):
return self.bottom - self.top
def with_margin(self, margin):
return BoundingBox(self.left - margin, self.top - margin, self.right + margin, self.bottom + margin)
def optimize_text_layout(ns, erfas, chaostreffs, size, svg):
font = ImageFont.truetype(ns.font, ns.font_size)
pil = ImageDraw(Image.new('P', size))
lboxes = {}
rboxes = {}
for city, location in erfas.items():
lbox = pil.textbbox((location[0] + ns.font_distance, location[1] - ns.font_size/2), city, font=font, anchor='lt')
lbox = BoundingBox(*lbox)
rbox = pil.textbbox((location[0] - ns.font_distance, location[1] - ns.font_size/2), city, font=font, anchor='rt')
rbox = BoundingBox(*rbox)
lboxes[city] = lbox
rboxes[city] = rbox
if ns.debug:
dr1 = etree.Element('rect', x=str(lbox.left), y=str(lbox.top), width=str(lbox.width), height=str(lbox.height))
dr1.set('class', 'debugleft')
dr2 = etree.Element('rect', x=str(rbox.left), y=str(rbox.top), width=str(rbox.width), height=str(rbox.height))
dr2.set('class', 'debugright')
svg.append(dr1)
svg.append(dr2)
unfinished = {c for c in erfas.keys()}
finished = {}
for city in list(unfinished):
margin_left = lboxes[city].with_margin(ns.dotsize_erfa)
i_left = sum([1 for c, b in lboxes.items() if c != city and b in margin_left])
i_left += sum([1 for c, b in rboxes.items() if c != city and b in margin_left])
i_left += sum([0.5 for c, loc in chaostreffs.items() if loc in margin_left])
margin_right = rboxes[city].with_margin(ns.dotsize_erfa)
i_right = sum([1 for c, b in lboxes.items() if c != city and b in margin_right])
i_right += sum([1 for c, b in rboxes.items() if c != city and b in margin_right])
i_right += sum([0.5 for c, loc in chaostreffs.items() if loc in margin_right])
if i_right == 0 and i_left > 0:
finished[city] = rboxes[city]
unfinished.discard(city)
elif i_left == 0:
if lboxes[city].left > size[0] * 0.8:
finished[city] = rboxes[city]
else:
finished[city] = lboxes[city]
unfinished.discard(city)
for a in list(unfinished):
if a not in unfinished:
continue
mla = lboxes[a].with_margin(ns.dotsize_erfa)
mra = rboxes[a].with_margin(ns.dotsize_erfa)
for b in list(unfinished.difference({a})):
if b not in unfinished:
continue
mlb = lboxes[b]
mrb = rboxes[b]
if mla not in mlb and mla not in mlb and mra not in mlb and mra not in mrb:
continue
if mra in mlb:
finished[a] = lboxes[a]
finished[b] = rboxes[b]
unfinished.discard(a)
unfinished.discard(b)
elif mla in mrb:
finished[a] = rboxes[a]
finished[b] = lboxes[b]
unfinished.discard(a)
unfinished.discard(b)
for city in list(unfinished):
margin_left = lboxes[city].with_margin(ns.dotsize_erfa)
margin_right = rboxes[city].with_margin(ns.dotsize_erfa)
i_left = sum([1 for c, b in finished.items() if c != city and b in margin_left])
i_left += sum([0.5 for c, loc in chaostreffs.items() if loc in margin_left])
i_right = sum([1 for c, b in finished.items() if c != city and b in margin_right])
i_right += sum([0.5 for c, loc in chaostreffs.items() if loc in margin_right])
if i_left <= i_right:
finished[city] = lboxes[city]
else:
finished[city] = rboxes[city]
unfinished.discard(city)
return finished
def create_svg(ns, bbox):
print('Creating SVG image')
# Convert from WGS84 lon, lat to chosen projection
transformer = pyproj.Transformer.from_crs('epsg:4326', ns.projection)
scalex = ns.scale_x
scaley = ns.scale_y
blt = transformer.transform(*bbox[0])
trt = transformer.transform(*bbox[1])
trans_bounding_box = [
(scalex*blt[0], scaley*trt[1]),
(scalex*trt[0], scaley*blt[1])
]
origin = jtm_bounding_box[0]
svg_box = (jtm_bounding_box[1][0] - origin[0], origin[1] - jtm_bounding_box[1][1])
origin = trans_bounding_box[0]
svg_box = (trans_bounding_box[1][0] - origin[0], origin[1] - trans_bounding_box[1][1])
shapes_states = []
files = os.listdir(source_states)
files = os.listdir(ns.cache_directory.shapes_states)
for f in files:
if not f.endswith('.json'):
continue
path = os.path.join(source_states, f)
path = os.path.join(ns.cache_directory.shapes_states, f)
with open(path, 'r') as sf:
shapedata = sf.read()
shape = json.loads(shapedata)
@ -175,11 +381,11 @@ def create_svg(source_states='shapes_states', source_countries='shapes_filtered'
shapes_states.append(ts)
shapes_countries = []
files = os.listdir(source_countries)
files = os.listdir(ns.cache_directory.shapes_filtered)
for f in files:
if not f.endswith('.json'):
continue
path = os.path.join(source_countries, f)
path = os.path.join(ns.cache_directory.shapes_filtered, f)
with open(path, 'r') as sf:
shapedata = sf.read()
shape = json.loads(shapedata)
@ -194,7 +400,7 @@ def create_svg(source_states='shapes_states', source_countries='shapes_filtered'
shapes_countries.append(ts)
chaostreffs = {}
with open(source_ct, 'r') as f:
with open(ns.cache_directory.chaostreff_info, 'r') as f:
ctdata = json.load(f)
for city, location in ctdata.items():
if location is None:
@ -203,7 +409,7 @@ def create_svg(source_states='shapes_states', source_countries='shapes_filtered'
chaostreffs[city] = (xt*scalex - origin[0], origin[1] - yt*scaley)
erfas = {}
with open(source_erfa, 'r') as f:
with open(ns.cache_directory.erfa_info, 'r') as f:
ctdata = json.load(f)
for city, location in ctdata.items():
if location is None:
@ -218,67 +424,117 @@ def create_svg(source_states='shapes_states', source_countries='shapes_filtered'
rectbox[1] = min(lat, rectbox[1])
rectbox[2] = max(lon, rectbox[2])
rectbox[3] = max(lat, rectbox[3])
SVG = f'''
<svg viewBox="0 0 {svg_box[0]} {svg_box[1]}" width="{svg_box[0]}" height="{svg_box[1]}"
xmlns="http://www.w3.org/2000/svg">
<rect x="{rectbox[0]}" y="{rectbox[1]}" width="{rectbox[3]-rectbox[1]}" height="{rectbox[2]-rectbox[0]}" stroke="none" fill="#759eb5" />
'''
svg = etree.Element('svg',
xmlns='http://www.w3.org/2000/svg',
viewBox=f'0 0 {svg_box[0]} {svg_box[1]}',
width=str(svg_box[0]), height=str(svg_box[1]))
# Render shortest shapes last s.t. Berlin, Hamburg and Bremen are rendered on top of their surrounding states
for shape in sorted(shapes_states, key=lambda x: -sum(len(s) for s in x)):
points = ' '.join([f'{lon},{lat}' for lon, lat in shape])
SVG += f'''
<polygon points="{points}" fill="#ffffff" stroke="#c3c3c3" stroke-width="3" />
'''
defs = etree.Element('defs')
style = etree.Element('style', type='text/css')
style.text = f'@import url({ns.stylesheet})'
defs.append(style)
svg.append(defs)
bg = etree.Element('rect',
x=str(rectbox[0]),
y=str(rectbox[1]),
width=str(rectbox[3]-rectbox[1]),
height=str(rectbox[2]-rectbox[0]))
bg.set('class', 'background')
svg.append(bg)
for shape in shapes_countries:
points = ' '.join([f'{lon},{lat}' for lon, lat in shape])
SVG += f'''
<polygon points="{points}" fill="#c3c3c3" stroke="#ffffff" stroke-width="3" />
'''
poly = etree.Element('polygon', points=points)
poly.set('class', 'country')
svg.append(poly)
# Render shortest shapes last s.t. Berlin, Hamburg and Bremen are rendered on top of their surrounding states
for shape in sorted(shapes_states, key=lambda x: -sum(len(s) for s in x)):
points = ' '.join([f'{lon},{lat}' for lon, lat in shape])
poly = etree.Element('polygon', points=points)
poly.set('class', 'state')
svg.append(poly)
for city, location in erfas.items():
SVG += f'''
<circle cx="{location[0]}" cy="{location[1]}" r="15" fill="#f47e1e" stroke="#ffffff" stroke-width="3" />
<!--<rect x="{location[0]+25}" y="{location[1]-15}" width="{len(city)*15}" height="30" stroke="green" stroke-width="1" fill="none" />
<rect x="{location[0]-25-len(city)*15}" y="{location[1]-15}" width="{len(city)*15}" height="30" stroke="red" stroke-width="1" fill="none" />-->
'''
circle = etree.Element('circle', cx=str(location[0]), cy=str(location[1]), r=str(ns.dotsize_erfa))
circle.set('class', 'erfa')
svg.append(circle)
for city, location in chaostreffs.items():
SVG += f'''
<circle cx="{location[0]}" cy="{location[1]}" r="8" fill="#f47e1e" stroke="#ffffff" stroke-width="3" />
'''
for city, location in erfas.items():
weight_right = sum([1 for x, y in erfas.values() if x > location[0] + 25 and x < location[0] + 25 + len(city)*15 and y > location[1] - 15 and y < location[1] + 25])
weight_left = sum([1 for x, y in erfas.values() if x < location[0] - 25 and x > location[0] - 25 - len(city)*15 and y > location[1] - 15 and y < location[1] + 25])
if weight_right > weight_left:
SVG += f'''
<text x="{location[0]-25}" y="{location[1]+7.5}" text-anchor="end" style="font-family: Liberation Sans; font-size: 25; font-weight: bold;">{city}</text>
'''
else:
SVG += f'''
<text x="{location[0]+25}" y="{location[1]+7.5}" style="font-family: Liberation Sans; font-size: 25; font-weight: bold;">{city}</text>
'''
circle = etree.Element('circle', cx=str(location[0]), cy=str(location[1]), r=str(ns.dotsize_treff))
circle.set('class', 'chaostreff')
svg.append(circle)
SVG += '</svg>'
print('Layouting labels')
texts = optimize_text_layout(ns, erfas, chaostreffs, (int(svg_box[0]), int(svg_box[1])), svg)
for city, box in texts.items():
text = etree.Element('text', x=str(box.left), y=str(box.top))
text.set('alignment-baseline', 'hanging')
text.set('class', 'erfalabel')
text.text = city
svg.append(text)
with open('map.svg', 'w') as mapfile:
mapfile.write(SVG)
print('Done, writing SVG')
with open('map.svg', 'wb') as mapfile:
root = etree.ElementTree(svg)
root.write(mapfile)
print('Done, writing PNG')
cairosvg.svg2png(url='map.svg', write_to='map.png')
print('Done')
bbox = []
fetch_erfas(bbox=bbox)
#fetch_chaostreffs(bbox=bbox)
#print(bbox)
#fetch_wikidata_states()
#fetch_wikidata_countries()
filter_boundingbox()
create_svg()
# Q347 P361 Q46
def main():
ap = argparse.ArgumentParser(sys.argv[0])
ap.add_argument('--cache-directory', type=CachePaths, default='cache', help='Path to the cache directory')
ap.add_argument('--update-borders', action='store_true', default=False, help='Update country borders from Wikidata')
ap.add_argument('--update-erfalist', action='store_true', default=False, help='Update Erfa/Chaostreff list from doku.ccc.de')
ap.add_argument('--bbox', type=float, nargs=4, default=None, help='Override map bounding box with <lon> <lat> <lon> <lat>')
ap.add_argument('--bbox-margin', type=float, default=0.3, help='Margin around the inferred bounding box. Ignored with --bbox')
ap.add_argument('--stylesheet', type=str, default='style/erfamap.css', help='Stylesheet for the generated SVG')
ap.add_argument('--font', type=str, default='style/concertone-regular.ttf', help='Name of the font used in the stylesheet.')
ap.add_argument('--font-size', type=int, default=30, help='Size of the font used in the stylesheet.')
ap.add_argument('--font-distance', type=int, default=25, help='Distance of labels from their dots center')
ap.add_argument('--dotsize-erfa', type=float, default=13, help='Radius of Erfa dots')
ap.add_argument('--dotsize-treff', type=float, default=8, help='Radius of Chaostreff dots')
ap.add_argument('--projection', type=str, default='epsg:4258', help='Map projection to convert the WGS84 coordinates to')
ap.add_argument('--scale-x', type=float, default=130, help='X axis scale to apply after projecting')
ap.add_argument('--scale-y', type=float, default=200, help='Y axis scale to apply after projecting')
ap.add_argument('--debug', action='store_true', default=False, help='Add debug information to the produced SVG')
ns = ap.parse_args(sys.argv[1:])
if ns.update_borders or not os.path.isdir(ns.cache_directory.shapes_countries):
if os.path.isdir(ns.cache_directory.shapes_countries):
shutil.rmtree(ns.cache_directory.shapes_countries)
fetch_wikidata_countries(target=ns.cache_directory.shapes_countries)
if ns.update_borders or not os.path.isdir(ns.cache_directory.shapes_states):
if os.path.isdir(ns.cache_directory.shapes_states):
shutil.rmtree(ns.cache_directory.shapes_states)
fetch_wikidata_states(target=ns.cache_directory.shapes_states)
if ns.update_erfalist or not os.path.isfile(ns.cache_directory.erfa_info):
if os.path.exists(ns.cache_directory.erfa_info):
os.unlink(ns.cache_directory.erfa_info)
fetch_erfas(target=ns.cache_directory.erfa_info, url=ERFA_URL)
if ns.update_erfalist or not os.path.isfile(ns.cache_directory.chaostreff_info):
if os.path.exists(ns.cache_directory.chaostreff_info):
os.unlink(ns.cache_directory.chaostreff_info)
fetch_erfas(target=ns.cache_directory.chaostreff_info, url=CHAOSTREFF_URL)
bbox = compute_bbox(ns)
filter_boundingbox(ns.cache_directory.shapes_countries, ns.cache_directory.shapes_filtered, bbox)
create_svg(ns, bbox)
if __name__ == '__main__':
main()

BIN
map.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 KiB

603
map.svg

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

6
requirements.txt Normal file
View file

@ -0,0 +1,6 @@
lxml
pyproj
geopy
cairosvg
tqdm
Pillow

Binary file not shown.

45
style/erfamap.css Normal file
View file

@ -0,0 +1,45 @@
rect.background {
fill: #759eb5;
stroke: none;
}
polygon.country {
fill: #c3c3c3;
stroke: #ffffff;
stroke-width: 2;
}
polygon.state {
fill: #ffffff;
stroke: #c3c3c3;
stroke-width: 2;
}
circle.erfa, circle.chaostreff {
fill: #f47e1e;
stroke: #ffffff;
stroke-width: 3;
}
@font-face {
font-family: 'concertone';
src: url('./concertone-regular.ttf');
}
text.erfalabel {
font-family: concertone;
font-size: 30px;
}
rect.debugleft {
stroke: red;
stroke-width: 1;
fill: none;
}
rect.debugright {
stroke: green;
stroke-width: 1;
fill: none;
}