├── .dockerignore ├── .editorconfig ├── .gitignore ├── BSH.md ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yaml ├── docs ├── almanac.md ├── depth.md ├── img │ ├── almanac.webp │ ├── avnav.webp │ ├── compass.svg │ ├── favicon.ico │ ├── garmin.webp │ ├── josm.webp │ ├── logo.webp │ ├── ochartsng.webp │ ├── opencpn.webp │ ├── raster.webp │ ├── tide-figures.webp │ ├── tides1.webp │ ├── tides2.webp │ ├── vector.webp │ └── webmap.webp ├── index.md ├── legal.md ├── lights.md ├── print.md ├── print │ ├── Juist.pdf │ ├── chart.webp │ └── layout.webp ├── screenshots.md ├── tides.md └── times.py ├── garmin └── marine │ ├── info │ ├── lines │ ├── options │ ├── points │ ├── polygons │ └── version ├── icons ├── .gitignore ├── 2_cones_base_together.svg ├── 2_cones_down.svg ├── 2_cones_point_together.svg ├── 2_cones_up.svg ├── 2_spheres.svg ├── TSS-arrow-dashed.svg ├── TSS-arrow.svg ├── VHF.svg ├── anchor.svg ├── arc-0-G.svg ├── arc-0-R.svg ├── arc-0-W.svg ├── arc-0-Y.svg ├── arc-1-G.svg ├── arc-1-R.svg ├── arc-1-W.svg ├── arc-1-Y.svg ├── arc-2-G.svg ├── arc-2-R.svg ├── arc-2-W.svg ├── arc-2-Y.svg ├── arc-3-G.svg ├── arc-3-R.svg ├── arc-3-W.svg ├── arc-3-Y.svg ├── arc-4-G.svg ├── arc-4-R.svg ├── arc-4-W.svg ├── arc-4-Y.svg ├── area-g.svg ├── area-m.svg ├── barrel.svg ├── besom_point_down.svg ├── besom_point_up.svg ├── bird.svg ├── breakers.svg ├── cable.svg ├── cairn-lmk.svg ├── cairn.svg ├── can.svg ├── caution.svg ├── chimney.svg ├── church.svg ├── circle.svg ├── clearance-safe.svg ├── clearance.svg ├── cliff.svg ├── column.svg ├── compass-inner.svg ├── compass-outer.svg ├── cone_point_down.svg ├── cone_point_up.svg ├── conical.svg ├── cross-lmk.svg ├── cross.svg ├── cupola.svg ├── current.svg ├── customs-limit.svg ├── cylinder.svg ├── dir-buoyage-a.svg ├── dir-buoyage-b.svg ├── dish_aerial.svg ├── dome.svg ├── ebb-tide.svg ├── eddies.svg ├── extra.mapcss ├── ferry.svg ├── fish.svg ├── fishingharbour.svg ├── flag.svg ├── flagstaff.svg ├── flare-stack.svg ├── flash.svg ├── flood-tide.svg ├── floodlight.svg ├── fogsignal.svg ├── foul.svg ├── kelp.svg ├── lattice.svg ├── light-major.svg ├── light-minor.svg ├── light.svg ├── light_float.svg ├── line-dashed.svg ├── line-solid.svg ├── marina.svg ├── marine-farm.svg ├── mast.svg ├── military.svg ├── monument.svg ├── no-anchor.svg ├── no-diving.svg ├── no-entry.svg ├── no-fishing.svg ├── notice-board.svg ├── obelisk.svg ├── obstruction.svg ├── overfalls.svg ├── perch-port.svg ├── perch-starboard.svg ├── perch.svg ├── pile.svg ├── pillar.svg ├── pilot-boarding.svg ├── pipeline.svg ├── platform.svg ├── pole.svg ├── post.svg ├── pylon.svg ├── radar-reflector.svg ├── radar_scanner.svg ├── radio-mast.svg ├── rescue.svg ├── rhombus.svg ├── rock-awash.svg ├── rock-covers.svg ├── rock-dangerous.svg ├── rock-submerged.svg ├── sandwaves.svg ├── seagrass.svg ├── seal.svg ├── shellfish.svg ├── spar.svg ├── sphere.svg ├── sphere_over_rhombus.svg ├── spherical.svg ├── spire.svg ├── square.svg ├── stake.svg ├── statue.svg ├── super-buoy.svg ├── tide-gauge.svg ├── tower-lmk.svg ├── tower.svg ├── triangle_point_down.svg ├── triangle_point_up.svg ├── watertower.svg ├── windmill.svg ├── windmotor.svg ├── windsock.svg ├── wiredrag.svg ├── withy-port.svg ├── withy-starboard.svg ├── wreck-dangerous.svg ├── wreck-hull_showing.svg ├── wreck-non-dangerous.svg ├── x-shape.svg ├── yacht-berth.svg └── yacht-club.svg ├── makefile ├── mapproxy ├── mapproxy.yaml └── seed.yaml ├── mkdocs.yml ├── osmand ├── batch-all.xml ├── depthcontourlines.addon.render.0.xml ├── depthcontourlines.addon.render.xml ├── makefile ├── marine.render.xml ├── nautical.render.0.xml ├── rendering_types-no.xml ├── rendering_types.0.xml ├── rendering_types.xml └── symbols.osm ├── qgis ├── bsh-contours.py ├── bsh.qgs ├── depth.qgs ├── paperchart.qpt ├── rws.qgs ├── s57.qgs └── schutzzonen.qgs ├── scripts ├── bsh_catalog.py ├── convert.py ├── coursedata.py ├── depth.py ├── filter.py ├── genicons.py ├── lightsectors.py ├── poster.py ├── requirements.txt ├── rwsget.py ├── s57.py ├── s57attributes.csv ├── s57objectclasses.csv ├── schutzzonen.py ├── sconvert.py ├── senc.py ├── update.py └── vconvert.py ├── tides ├── README.md ├── makefile ├── mapproxy.yaml ├── seed.yaml └── tides.qgs ├── web ├── .gitignore ├── package.json ├── src │ ├── allgemeine.json │ ├── ausstiege.json │ ├── ausstiegeA.json │ ├── ausstiegeK.json │ ├── besondere.json │ ├── gpx.js │ ├── grid.js │ ├── gruen.json │ ├── icon.png │ ├── icon.svg │ ├── index.html │ ├── index.js │ ├── kite.json │ ├── leaflet-timeline-slider.js │ ├── legend.js │ ├── legend.less │ ├── manifest.json │ ├── nightmode.css │ ├── nightmode.js │ ├── rot.json │ ├── routen.json │ ├── store.js │ ├── style.css │ ├── utils.js │ └── vector.js └── webpack.config.js └── www ├── arrow-groundtrack.svg ├── arrow-tide.svg ├── arrow-watertrack.svg ├── arrow.svg ├── arrow1.svg ├── arrow2.svg ├── arrow3.svg ├── charttools.css ├── charttools.js ├── circle.svg ├── dr.svg ├── ep.svg ├── favicon.ico ├── fix.svg ├── line-2.svg ├── line-3.svg ├── line-dashed.svg ├── line.svg ├── map.html ├── style.json ├── vector.html ├── vector.js ├── wp.svg └── x.svg /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | tab_width = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [makefile] 13 | indent_style = tab 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | 18 | [*.xml] 19 | indent_style = tab 20 | 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /* 2 | !/icons/ 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:rolling 2 | ENV LANG="C.UTF-8" DEBIAN_FRONTEND="noninteractive" 3 | RUN echo "force-unsafe-io" > /etc/dpkg/dpkg.cfg.d/02apt-speedup && echo "Acquire::http {No-Cache=True;};" > /etc/apt/apt.conf.d/no-cache && echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/no-lang 4 | RUN apt-get -y update 5 | RUN apt-get -y upgrade && apt-get clean 6 | RUN apt-get install -y make python3-pip qgis-server fonts-open-sans tippecanoe cargo && apt-get clean 7 | #RUN apt-get install -y mapproxy make && apt-get clean 8 | RUN pip install MapProxy==2.0.2 shapely --break-system-packages 9 | ENV QGIS_SERVER_ADDRESS=0.0.0.0 10 | EXPOSE 8000/tcp 11 | USER ubuntu:ubuntu 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Free Nautical Charts 2 | 3 | Conversion of chart data from different formats and sources to create free nautical charts. 4 | 5 | :exclamation: This is still very work in progress! 6 | 7 | :point_right: [Documentation](http://freenauticalchart.net/download/) 8 | 9 | ![example map](docs/img/vector.png) 10 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | qgis: 3 | image: qgis-server 4 | build: 5 | context: . 6 | restart: unless-stopped 7 | ports: 8 | - 8000:8000 9 | - 8001:8001 10 | volumes: 11 | - .:/data 12 | working_dir: /data 13 | command: "make -j qgis mapproxy" 14 | -------------------------------------------------------------------------------- /docs/almanac.md: -------------------------------------------------------------------------------- 1 | # Nautical Almanac 2 | 3 | For those interested in celestial navigation I created a [Nautical Alamanac in its own project](https://github.com/quantenschaum/nautical_almanac). 4 | 5 | The current almanac can be downloaded here. 6 | 7 | - [Nautical Alamanac 2025 PDF](Nautical-Almanac-2025.pdf) 8 | - [Daily Pages 2025 TXT](daily-pages-2025.txt) 9 | 10 | ![Nautical Alamanac sample page](img/almanac.webp) 11 | -------------------------------------------------------------------------------- /docs/img/almanac.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantenschaum/mapping/2afcb7553ef2696d02e868faec8a1cfb847ef341/docs/img/almanac.webp -------------------------------------------------------------------------------- /docs/img/avnav.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantenschaum/mapping/2afcb7553ef2696d02e868faec8a1cfb847ef341/docs/img/avnav.webp -------------------------------------------------------------------------------- /docs/img/compass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 42 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /docs/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantenschaum/mapping/2afcb7553ef2696d02e868faec8a1cfb847ef341/docs/img/favicon.ico -------------------------------------------------------------------------------- /docs/img/garmin.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantenschaum/mapping/2afcb7553ef2696d02e868faec8a1cfb847ef341/docs/img/garmin.webp -------------------------------------------------------------------------------- /docs/img/josm.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantenschaum/mapping/2afcb7553ef2696d02e868faec8a1cfb847ef341/docs/img/josm.webp -------------------------------------------------------------------------------- /docs/img/logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantenschaum/mapping/2afcb7553ef2696d02e868faec8a1cfb847ef341/docs/img/logo.webp -------------------------------------------------------------------------------- /docs/img/ochartsng.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantenschaum/mapping/2afcb7553ef2696d02e868faec8a1cfb847ef341/docs/img/ochartsng.webp -------------------------------------------------------------------------------- /docs/img/opencpn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantenschaum/mapping/2afcb7553ef2696d02e868faec8a1cfb847ef341/docs/img/opencpn.webp -------------------------------------------------------------------------------- /docs/img/raster.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantenschaum/mapping/2afcb7553ef2696d02e868faec8a1cfb847ef341/docs/img/raster.webp -------------------------------------------------------------------------------- /docs/img/tide-figures.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantenschaum/mapping/2afcb7553ef2696d02e868faec8a1cfb847ef341/docs/img/tide-figures.webp -------------------------------------------------------------------------------- /docs/img/tides1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantenschaum/mapping/2afcb7553ef2696d02e868faec8a1cfb847ef341/docs/img/tides1.webp -------------------------------------------------------------------------------- /docs/img/tides2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantenschaum/mapping/2afcb7553ef2696d02e868faec8a1cfb847ef341/docs/img/tides2.webp -------------------------------------------------------------------------------- /docs/img/vector.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantenschaum/mapping/2afcb7553ef2696d02e868faec8a1cfb847ef341/docs/img/vector.webp -------------------------------------------------------------------------------- /docs/img/webmap.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantenschaum/mapping/2afcb7553ef2696d02e868faec8a1cfb847ef341/docs/img/webmap.webp -------------------------------------------------------------------------------- /docs/lights.md: -------------------------------------------------------------------------------- 1 | # Rendering of Sector Lights 2 | 3 | Currently, it is [not possible to render light sectors](https://github.com/osmandapp/OsmAnd/issues/16894) directly in OsmAnd. 4 | 5 | The workaround to get light sectors displayed is to create dedicated lines for the sector limits and sector arcs based on the [attributes of sector lights](https://wiki.openstreetmap.org/wiki/Seamarks/Lights#Sectored_light_attributes) in the OSM database. 6 | 7 | The procedure is as follows: 8 | 9 | - download OSM data with [JOSM](https://josm.openstreetmap.de/) (use [overpass](https://overpass-turbo.eu/) to filter for lights only) 10 | - save as OSM XML file 11 | - run [lightsectors.py](lightsectors.py) on this file 12 | - review the output file in JOSM (optional) 13 | - generate an OBF with the [OsmAndMapCreator](https://osmand.net/docs/versions/map-creator) using these [rendering_types.xml](rendering_types.xml) from the output file 14 | 15 | :exclamation: The sectors in this OBF are displayed in OsmAnd with the [marine style](marine.render.xml) only. The sections in `marine.render.xml` and in `rendering_types.xml` responsible for light sector rendering can be found by searing for `lightsector`. 16 | 17 | There are custom settings in the hide section of the map config to hide 18 | 19 | - *light sectors* - hides the sector limits and arcs 20 | - *light sector sources* - hides light source icons, the label is still displayed 21 | 22 | The icons and labels have a higher render priority and get displayed above other icons/labels, so you might want to hide the *light sector sources* to be able to see the actual beacon/lighthouse icon. 23 | 24 | The script creates 25 | 26 | - a colored arc for the sector with characteristics label 27 | - lines at sector limits with bearing label 28 | - a colored line for directional lights with bearing and characteristics label 29 | - a marker at the light source with characteristics label 30 | 31 | :point_right: An OBF can be found in the [download section](index.md#vector-charts). 32 | 33 | In OsmAnd light sectors look like this. 34 | 35 | ![light sectors](img2/lightsectors.png) 36 | 37 | overpass query 38 | 39 | ``` 40 | [out:xml][timeout:90]; 41 | ( 42 | nwr["seamark:type"="light_major"]; 43 | nwr[~"seamark:type"~"landmark|light|beacon"]["seamark:light:range"]; 44 | nwr[~"seamark:type"~"landmark|light|beacon"]["seamark:light:1:range"]; 45 | ); 46 | (._;>;); 47 | out meta; 48 | ``` 49 | -------------------------------------------------------------------------------- /docs/print.md: -------------------------------------------------------------------------------- 1 | # Printing Charts 2 | 3 | How to print your own charts [like this one](print/Juist.pdf). 4 | 5 | ![printed chart](print/chart.webp) 6 | 7 | You can print your own custom charts with the classical lat/lon zebra border as follows. 8 | 9 | 1. Install [QGIS](https://qgis.org/) on your computer. 10 | 2. Download the [data package](qmap-data.zip){:download} containing all necessary file. 11 | 3. Unzip the data package. 12 | 4. Open `bsh.qgs` with QGIS. 13 | 5. Select `Project > Layout Manager`. 14 | 6. Doubleclick the `paperchart` layout and the layout editor will open. 15 | ![layout editor](print/layout.webp) 16 | 7. Adjust the layout to your liking, select the part of the map you want to print (use the move content tool (C)). 17 | 8. Export as PDF. 18 | 9. Print the PDF (direct printing from QGIS may work, but PDF is usually more reliable and you can save it to print it again). 19 | -------------------------------------------------------------------------------- /docs/print/Juist.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantenschaum/mapping/2afcb7553ef2696d02e868faec8a1cfb847ef341/docs/print/Juist.pdf -------------------------------------------------------------------------------- /docs/print/chart.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantenschaum/mapping/2afcb7553ef2696d02e868faec8a1cfb847ef341/docs/print/chart.webp -------------------------------------------------------------------------------- /docs/print/layout.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantenschaum/mapping/2afcb7553ef2696d02e868faec8a1cfb847ef341/docs/print/layout.webp -------------------------------------------------------------------------------- /docs/screenshots.md: -------------------------------------------------------------------------------- 1 | # Screenshots 2 | 3 | How do these charts look like in various applications? 4 | 5 | ## OsmAnd 6 | 7 | Vector Chart 8 | 9 | ![osmand vector chart](img/vector.webp) 10 | 11 | Raster Overlay 12 | 13 | ![osmand raster chart](img/raster.webp) 14 | 15 | ## AvNav 16 | 17 | Raster Chart 18 | 19 | ![AvNav](img/avnav.webp) 20 | 21 | Vector Charts via Ocharts(NG) 22 | 23 | ![OchartsNG](img/ochartsng.webp) 24 | 25 | ## OpenCPN 26 | 27 | Vectop Chart 28 | 29 | ![OpenCPN](img/opencpn.webp) 30 | 31 | ## JOSM 32 | 33 | Imagery Layer 34 | 35 | ![JOSM](img/josm.webp) 36 | 37 | ## Garmin 38 | 39 | Small handheld device eTrex Vista HCx 40 | 41 | ![garmin](img/garmin.webp) 42 | -------------------------------------------------------------------------------- /docs/tides.md: -------------------------------------------------------------------------------- 1 | # Tidal Atlas 2 | 3 | The BSH provides tidal current data for the North Sea, the Channel and the Germany Bight (higher resolution). 4 | The presentation of this data in the [GeoSeaPortal](https://www.geoseaportal.de/mapapps/resources/apps/gezeitenstromatlas) is not very usable, you can neither select the dataset nor the hour of the tide, so it's pretty useless. But the raw data is available at . 5 | 6 | I used QGIS to render the average tidal current field and added annotations for set and drift, so you can choose the hour of the tide, depending on the zoom level you get an overview of the tidal current, and you can directly read off set and drift at the desired position (when zoomed in). 7 | 8 | ![tides overview](img/tides1.webp) 9 | 10 | ![tides detail](img/tides2.webp) 11 | 12 | The chart looks as shown above. The slider is used to select the hour before/after high water at Helgoland. The arrows in size and color show average set and drift, The number below the arrow is the average set (direction), the numbers above the arrow are the drift (velocity) in 10th of a knot at neaps (before the dot) and at springs (after the dot). 13 | 14 | ![tide figures](img/tide-figures.webp) 15 | 16 | When the slider is set to `Figures`, all tide arrows are shown simultaneously revealing the tide figure for the location. The numbers are the hour before/after HW. 17 | -------------------------------------------------------------------------------- /docs/times.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | from datetime import datetime 5 | import os.path 6 | import re 7 | import sys 8 | 9 | 10 | def replace(file): 11 | with open(file) as f: 12 | input = f.read() 13 | 14 | output = [] 15 | for line in input.splitlines(): 16 | # line=line.strip() 17 | m = re.search(r"\[.+\]\((.+?)\)\{:download\}", line) 18 | if m: 19 | try: 20 | # print(m.groups()) 21 | filename = m.group(1) 22 | # filename="index.md" 23 | mtime = os.path.getmtime(filename) 24 | fsize = os.path.getsize(filename) 25 | line = (line 26 | .replace('{:download}', f" ({datetime.fromtimestamp(mtime):%Y-%m-%d}/{fsize/1e6:.1f}MB)") 27 | .replace(f'({filename})',f'({filename}?t={int(mtime)})')) 28 | except: 29 | pass 30 | # print(line) 31 | output.append(line + "\n") 32 | 33 | with open(file, "w") as f: 34 | f.writelines(output) 35 | 36 | def main(): 37 | for f in sys.argv[1:]: 38 | replace(f) 39 | 40 | 41 | main() 42 | -------------------------------------------------------------------------------- /garmin/marine/info: -------------------------------------------------------------------------------- 1 | summary: nautical style for QMAP 2 | 3 | version=1.0 4 | 5 | description { 6 | style by https://waddenzee.duckdns.org/ 7 | } 8 | -------------------------------------------------------------------------------- /garmin/marine/lines: -------------------------------------------------------------------------------- 1 | contour=depth & depth=2.0 { add mkgmap:xt-depth='${depth}m'; } [0x010302 resolution 20] 2 | contour=depth & depth=5.0 { add mkgmap:xt-depth='${depth}m'; } [0x010303 resolution 18] 3 | contour=depth { add mkgmap:xt-depth='${depth}m'; } [0x010301 resolution 20] 4 | -------------------------------------------------------------------------------- /garmin/marine/options: -------------------------------------------------------------------------------- 1 | #levels = 0:24, 1:20, 2:18, 3:16 2 | # More levels may make zooming smoother, but it will generate larger map tiles. 3 | levels = 0:24, 1:22, 2:21, 3:20, 4:19, 5:18, 6:16 4 | 5 | overview-levels = 6:17, 7:16, 8:13 6 | #extra-used-tags= 7 | 8 | -------------------------------------------------------------------------------- /garmin/marine/polygons: -------------------------------------------------------------------------------- 1 | contourarea=depth & areatype=-1 { add mkgmap:xt-depth='0m'; } [0x010301 resolution 18] 2 | contourarea=depth & areatype=0 { add mkgmap:xt-depth='2m'; } [0x010302 resolution 18] 3 | contourarea=depth & areatype=2 { add mkgmap:xt-depth='5m'; } [0x010304 resolution 18] 4 | contourarea=depth & areatype=5 { add mkgmap:xt-depth='10m'; } [0x010305 resolution 18] 5 | contourarea=depth & areatype=10 { } [0x010306 resolution 18] 6 | -------------------------------------------------------------------------------- /garmin/marine/version: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /icons/.gitignore: -------------------------------------------------------------------------------- 1 | /* 2 | /gen 3 | !/*.svg 4 | -------------------------------------------------------------------------------- /icons/2_cones_base_together.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 44 | 48 | 52 | 56 | 60 | 64 | 68 | 69 | -------------------------------------------------------------------------------- /icons/2_cones_down.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 44 | 48 | 52 | 56 | 60 | 64 | 68 | 69 | -------------------------------------------------------------------------------- /icons/2_cones_point_together.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 43 | 47 | 51 | 55 | 59 | 63 | 67 | 68 | -------------------------------------------------------------------------------- /icons/2_cones_up.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 43 | 47 | 51 | 55 | 59 | 63 | 67 | 68 | -------------------------------------------------------------------------------- /icons/2_spheres.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 44 | 48 | 52 | 58 | 64 | 70 | 76 | 77 | -------------------------------------------------------------------------------- /icons/TSS-arrow-dashed.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 39 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /icons/TSS-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 39 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /icons/VHF.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 17 | 21 | 22 | 26 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /icons/arc-0-G.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 37 | 44 | 51 | 52 | 54 | 68 | 69 | -------------------------------------------------------------------------------- /icons/arc-0-R.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 37 | 44 | 51 | 52 | 54 | 68 | 69 | -------------------------------------------------------------------------------- /icons/arc-0-W.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 37 | 44 | 51 | 52 | 54 | 68 | 69 | -------------------------------------------------------------------------------- /icons/arc-0-Y.svg: -------------------------------------------------------------------------------- 1 | arc-0-W.svg -------------------------------------------------------------------------------- /icons/arc-1-G.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 37 | 44 | 51 | 52 | 54 | 68 | 69 | -------------------------------------------------------------------------------- /icons/arc-1-R.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 37 | 44 | 51 | 52 | 54 | 68 | 69 | -------------------------------------------------------------------------------- /icons/arc-1-W.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 37 | 44 | 51 | 52 | 54 | 68 | 69 | -------------------------------------------------------------------------------- /icons/arc-1-Y.svg: -------------------------------------------------------------------------------- 1 | arc-1-W.svg -------------------------------------------------------------------------------- /icons/arc-2-G.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 37 | 44 | 51 | 52 | 54 | 68 | 69 | -------------------------------------------------------------------------------- /icons/arc-2-R.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 37 | 44 | 51 | 52 | 54 | 68 | 69 | -------------------------------------------------------------------------------- /icons/arc-2-W.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 37 | 44 | 51 | 52 | 54 | 68 | 69 | -------------------------------------------------------------------------------- /icons/arc-2-Y.svg: -------------------------------------------------------------------------------- 1 | arc-2-W.svg -------------------------------------------------------------------------------- /icons/arc-3-G.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 37 | 44 | 51 | 52 | 54 | 68 | 69 | -------------------------------------------------------------------------------- /icons/arc-3-R.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 37 | 44 | 51 | 52 | 54 | 68 | 69 | -------------------------------------------------------------------------------- /icons/arc-3-W.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 37 | 44 | 51 | 52 | 54 | 68 | 69 | -------------------------------------------------------------------------------- /icons/arc-3-Y.svg: -------------------------------------------------------------------------------- 1 | arc-3-W.svg -------------------------------------------------------------------------------- /icons/arc-4-G.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 37 | 44 | 51 | 52 | 54 | 68 | 69 | -------------------------------------------------------------------------------- /icons/arc-4-R.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 37 | 44 | 51 | 52 | 54 | 68 | 69 | -------------------------------------------------------------------------------- /icons/arc-4-W.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 37 | 44 | 51 | 52 | 54 | 68 | 69 | -------------------------------------------------------------------------------- /icons/arc-4-Y.svg: -------------------------------------------------------------------------------- 1 | arc-4-W.svg -------------------------------------------------------------------------------- /icons/area-g.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 35 | 42 | 49 | 50 | 54 | 59 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /icons/area-m.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 35 | 42 | 49 | 50 | 53 | 58 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /icons/bird.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 33 | 40 | 47 | 48 | 51 | 56 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /icons/cable.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 35 | 42 | 49 | 50 | 54 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /icons/cairn-lmk.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 42 | 48 | 54 | 60 | 66 | 72 | 78 | 82 | 88 | 89 | -------------------------------------------------------------------------------- /icons/cairn.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 43 | 49 | 55 | 61 | 67 | 73 | 79 | 83 | 89 | 90 | -------------------------------------------------------------------------------- /icons/chimney.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 12 | 16 | 20 | 21 | 25 | 29 | 30 | 34 | 38 | 39 | 43 | 47 | 48 | 52 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /icons/clearance-safe.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 16 | 19 | 23 | 27 | 28 | 32 | 36 | 37 | 41 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /icons/clearance.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 16 | 19 | 22 | 26 | 27 | 30 | 34 | 35 | 39 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /icons/cliff.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 35 | 42 | 49 | 50 | 55 | 56 | -------------------------------------------------------------------------------- /icons/cone_point_down.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 43 | 47 | 51 | 55 | 56 | -------------------------------------------------------------------------------- /icons/cone_point_up.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 43 | 47 | 51 | 55 | 56 | -------------------------------------------------------------------------------- /icons/cross.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 43 | 47 | 52 | 57 | 62 | 67 | 68 | -------------------------------------------------------------------------------- /icons/customs-limit.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 17 | 21 | 22 | 26 | 30 | 31 | 35 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /icons/cylinder.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 43 | 47 | 51 | 55 | 59 | 63 | 67 | 68 | -------------------------------------------------------------------------------- /icons/dir-buoyage-a.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 32 | -------------------------------------------------------------------------------- /icons/dir-buoyage-b.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 17 | 23 | 29 | 35 | 36 | -------------------------------------------------------------------------------- /icons/eddies.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 33 | 40 | 47 | 48 | 49 | 53 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /icons/ferry.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 17 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /icons/fish.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 33 | 40 | 47 | 48 | 49 | 53 | 55 | 59 | 63 | 64 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /icons/flash.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 33 | 40 | 47 | 48 | 49 | 53 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /icons/lattice.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 44 | 48 | 52 | 53 | 57 | 61 | 65 | 69 | 75 | 76 | -------------------------------------------------------------------------------- /icons/light-major.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 17 | 23 | 24 | -------------------------------------------------------------------------------- /icons/light-minor.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /icons/light_float.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 44 | 45 | 48 | 52 | 53 | 57 | 63 | 64 | -------------------------------------------------------------------------------- /icons/line-dashed.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /icons/line-solid.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /icons/no-entry.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | -------------------------------------------------------------------------------- /icons/notice-board.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 19 | 25 | 29 | 33 | 34 | 38 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /icons/obstruction.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 35 | 42 | 49 | 50 | 54 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /icons/perch-port.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 42 | 46 | 50 | 54 | 55 | -------------------------------------------------------------------------------- /icons/perch-starboard.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 42 | 46 | 50 | 54 | 55 | -------------------------------------------------------------------------------- /icons/perch.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 43 | 47 | 51 | 55 | 59 | 63 | 64 | -------------------------------------------------------------------------------- /icons/pilot-boarding.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 18 | 20 | 22 | 25 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /icons/pipeline.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 37 | 41 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /icons/platform.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 17 | 21 | 22 | 26 | 30 | 31 | 35 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /icons/pole.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 43 | 47 | 51 | 55 | 59 | 63 | 64 | -------------------------------------------------------------------------------- /icons/post.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 43 | 47 | 51 | 55 | 59 | 63 | 64 | -------------------------------------------------------------------------------- /icons/pylon.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 17 | 20 | 23 | 27 | 28 | 31 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /icons/radar-reflector.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 17 | 21 | 22 | 26 | 30 | 31 | 35 | 39 | 40 | 44 | 48 | 49 | 53 | 57 | 58 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /icons/rescue.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 17 | 21 | 25 | 26 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /icons/rock-covers.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 12 | 16 | 20 | 21 | 25 | 29 | 30 | 34 | 38 | 39 | 43 | 47 | 48 | 52 | 56 | 57 | 61 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /icons/rock-dangerous.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 39 | 43 | 44 | 49 | 53 | 54 | 59 | 60 | -------------------------------------------------------------------------------- /icons/rock-submerged.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 17 | 18 | 22 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /icons/sandwaves.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 18 | 22 | 26 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /icons/seal.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 20 | 21 | -------------------------------------------------------------------------------- /icons/shellfish.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 17 | 21 | 25 | 26 | 30 | 34 | 35 | 39 | 43 | 44 | 48 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /icons/sphere.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 43 | 47 | 53 | 57 | 63 | 64 | -------------------------------------------------------------------------------- /icons/sphere_over_rhombus.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 64 | 68 | 72 | 76 | 82 | 88 | 89 | -------------------------------------------------------------------------------- /icons/spire.svg: -------------------------------------------------------------------------------- 1 | church.svg -------------------------------------------------------------------------------- /icons/stake.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 43 | 47 | 51 | 55 | 59 | 63 | 64 | -------------------------------------------------------------------------------- /icons/triangle_point_down.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 43 | 47 | 51 | 55 | 60 | 65 | 69 | 70 | -------------------------------------------------------------------------------- /icons/triangle_point_up.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 43 | 47 | 51 | 55 | 60 | 65 | 69 | 70 | -------------------------------------------------------------------------------- /icons/watertower.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 17 | 21 | 24 | 28 | 29 | 30 | 34 | 38 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /icons/windmill.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 18 | 19 | 23 | 27 | 28 | 32 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /icons/wiredrag.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 42 | 47 | 48 | -------------------------------------------------------------------------------- /icons/withy-port.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 42 | 46 | 50 | 54 | 58 | 59 | -------------------------------------------------------------------------------- /icons/withy-starboard.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 42 | 46 | 50 | 54 | 58 | 59 | -------------------------------------------------------------------------------- /icons/wreck-hull_showing.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 12 | 16 | 20 | 21 | 25 | 29 | 30 | 34 | 38 | 39 | 43 | 47 | 48 | 52 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /icons/wreck-non-dangerous.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 17 | 18 | 22 | 26 | 27 | 31 | 35 | 36 | 40 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /icons/x-shape.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 43 | 47 | 51 | 55 | 60 | 65 | 66 | -------------------------------------------------------------------------------- /icons/yacht-berth.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 12 | 16 | 20 | 21 | 25 | 29 | 30 | 34 | 38 | 39 | 43 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /icons/yacht-club.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 17 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /mapproxy/mapproxy.yaml: -------------------------------------------------------------------------------- 1 | globals: 2 | image: 3 | paletted: false 4 | resampling_method: bilinear 5 | cache: 6 | link_single_color_images: true 7 | meta_size: [ 6,6 ] 8 | meta_buffer: 200 9 | bulk_meta_tiles: true 10 | base_dir: ../cache_data 11 | tiles: 12 | expires_hours: 24 13 | http: 14 | client_timeout: 300 15 | 16 | services: 17 | tms: 18 | wms: 19 | demo: 20 | 21 | sources: 22 | qgis0: 23 | type: wms 24 | req: 25 | url: http://localhost:8000/ 26 | transparent: true 27 | map: bsh.qgs 28 | coverage: 29 | datasource: ../data/bsh.gpkg 30 | where: recind="Overview" 31 | srs: 'EPSG:4326' 32 | # image: 33 | # transparent_color: '#f9ecc0' 34 | qgis1: 35 | # shifted usage band as underlay 36 | type: wms 37 | req: 38 | url: http://localhost:8000/ 39 | transparent: true 40 | map: bsh1.qgs 41 | coverage: 42 | datasource: ../data/bsh.gpkg 43 | where: recind="Overview" 44 | srs: 'EPSG:4326' 45 | qgis2: 46 | # shifted usage band as underlay 47 | type: wms 48 | req: 49 | url: http://localhost:8000/ 50 | transparent: true 51 | map: bsh2.qgs 52 | coverage: 53 | datasource: ../data/bsh.gpkg 54 | where: recind="Overview" 55 | srs: 'EPSG:4326' 56 | rws: 57 | type: wms 58 | req: 59 | url: http://localhost:8000/ 60 | transparent: true 61 | map: rws.qgs 62 | coverage: 63 | datasource: ../data/rws-covr.gpkg 64 | where: CATCOV=1 65 | srs: 'EPSG:4326' 66 | 67 | caches: 68 | qmap-de: 69 | sources: [ 'qgis2:BSH','qgis1:BSH','qgis0:BSH' ] 70 | grids: [ GLOBAL_WEBMERCATOR ] 71 | cache: 72 | type: mbtiles 73 | qmap-nl: 74 | sources: [ 'rws:enc,vwm' ] 75 | grids: [ GLOBAL_WEBMERCATOR ] 76 | cache: 77 | type: mbtiles 78 | 79 | layers: 80 | - name: qmap-de 81 | title: "QMAP DE" 82 | sources: [ qmap-de ] 83 | - name: qmap-nl 84 | title: "QMAP NL" 85 | sources: [ qmap-nl ] 86 | -------------------------------------------------------------------------------- /mapproxy/seed.yaml: -------------------------------------------------------------------------------- 1 | seeds: 2 | overview: 3 | caches: [ qmap-de ] 4 | levels: [ 7,8,9 ] # <=7 5 | coverages: [ overview ] 6 | general: 7 | caches: [ qmap-de ] 8 | levels: [ 10 ] # 10 9 | coverages: [ general ] 10 | coastal: 11 | caches: [ qmap-de ] 12 | levels: [ 11,12 ] # 11,13 13 | coverages: [ coastal ] 14 | # for following levels also seed lower level because there are 15 | # "holes" in the coverage and allow 2 steps overzoom, 16 | # 2 zoom levels blow are underlayed in the mapproxy config 17 | # so, a lower level map is shown in the background 18 | approach: 19 | caches: [ qmap-de ] 20 | levels: [ 13,14,15 ] # 13,14 21 | coverages: [ approach ] 22 | harbour: 23 | caches: [ qmap-de ] 24 | levels: [ 13,14,15,16,17 ] # 15,16 25 | coverages: [ harbour ] 26 | berthing: 27 | caches: [ qmap-de ] 28 | levels: [ 13,14,15,16,17,18 ] # >=17 29 | coverages: [ berthing ] 30 | rws: 31 | caches: [ qmap-nl ] 32 | levels: 33 | from: 6 34 | to: 16 35 | coverages: [ rws ] 36 | 37 | coverages: 38 | overview: 39 | datasource: ../data/bsh.gpkg 40 | where: RECIND="Overview" 41 | srs: 'EPSG:4326' 42 | general: 43 | datasource: ../data/bsh.gpkg 44 | where: RECIND="General" 45 | srs: 'EPSG:4326' 46 | coastal: 47 | datasource: ../data/bsh.gpkg 48 | where: RECIND="Coastal" 49 | srs: 'EPSG:4326' 50 | approach: 51 | datasource: ../data/bsh.gpkg 52 | where: RECIND="Approach" 53 | srs: 'EPSG:4326' 54 | harbour: 55 | datasource: ../data/bsh.gpkg 56 | where: RECIND="Harbour" 57 | srs: 'EPSG:4326' 58 | berthing: 59 | datasource: ../data/bsh.gpkg 60 | where: RECIND="Berthing" 61 | srs: 'EPSG:4326' 62 | rws: 63 | datasource: ../data/rws-covr.gpkg 64 | where: CATCOV=1 65 | srs: 'EPSG:4326' 66 | -------------------------------------------------------------------------------- /osmand/batch-all.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 21 | 22 | -------------------------------------------------------------------------------- /osmand/makefile: -------------------------------------------------------------------------------- 1 | 2 | xmls: nautical.render.xml depthcontourlines.addon.render.xml rendering_types.xml 3 | 4 | %.render.xml: 5 | wget -O $(subst .xml,.0.xml,$@) https://github.com/osmandapp/OsmAnd-resources/raw/master/rendering_styles/$@ 6 | 7 | rendering_types.xml: 8 | wget -O $(subst .xml,.0.xml,$@) https://github.com/osmandapp/OsmAnd-resources/raw/master/obf_creation/rendering_types.xml 9 | -------------------------------------------------------------------------------- /scripts/bsh_catalog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | import os.path 5 | from pyquery import PyQuery as pq 6 | 7 | from filter import save_json 8 | 9 | def main(): 10 | xml=pq(filename=os.path.dirname(__file__)+'/../data/bsh/catalog.html') 11 | # print(xml) 12 | catalog={} 13 | for enc in xml('enc_entry'): 14 | enc=pq(enc) 15 | # print(enc) 16 | meta={c.tag:c.text for c in enc('*') if 'gml' not in c.tag and c.text and c.text.strip()} 17 | print(meta['c_number']) 18 | catalog[meta['c_number']]=meta 19 | 20 | save_json(os.path.dirname(__file__)+'/../data/bsh/catalog.json',catalog,indent=2) 21 | 22 | if __name__=='__main__': 23 | main() 24 | -------------------------------------------------------------------------------- /scripts/depth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | import os,sys 5 | from pyquery import PyQuery as pq 6 | import pendulum 7 | from functools import partial 8 | 9 | def process(fn): 10 | x=pq(filename=fn) 11 | 12 | attrs={ 13 | # 'date':['Date',pendulum.parse], 14 | # 'time':['Time',partial(pendulum.parse,exact=True)], 15 | 'gpsalt':['Corrected_Altitude_GPS_Antenna',float], 16 | 'ellalt':['Difference_Ellipsoid_Altitude',float], 17 | 'valid':['Depth_Valid',lambda s:s=='true'], 18 | 'lat':['Corrected_Latitude',float], 19 | 'lon':['Corrected_Longitude',float], 20 | 'north':['Corrected_X_North',float], 21 | 'east':['Corrected_Y_East',float], 22 | 'wlc1':['Water_Level_Corr1',float], 23 | 'wlc2':['Water_Level_Corr2',float], 24 | 'wlc3':['Water_Level_Corr3',float], 25 | 'wlc4':['Water_Level_Corr4',float], 26 | 'f_high':['Backscattering_high_Freq',float], 27 | 'f_low':['Backscattering_low_Freq',float], 28 | 'd_high':['Corrected_Depth_high_Freq_onFootprint',float], 29 | 'd_low':['Corrected_Depth_low_Freq_onFootprint',float], 30 | 'd_acc':['Accuracy_Depth',float], 31 | 'p_acc':['Accuracy_Position',float], 32 | } 33 | 34 | for d in x('*'): 35 | if d.tag=="Depth": 36 | q=pq(d) 37 | data={k:v[1](q.attr(v[0])) for k,v in attrs.items()} 38 | if data['valid']: 39 | # print(data) 40 | print(' '.join(str(data[k]) for k in fields.split())) 41 | 42 | fields='east north d_high d_low wlc1 wlc2' 43 | 44 | def main(): 45 | # process('data/Verm_Single_Beam_Nordsee_2024_9342400/9342400_Kontrollprofile/9342400_111_11_9004_21-06-2024_09-53-00.XML') 46 | print(fields) 47 | for f in sys.argv: 48 | process(f) 49 | 50 | main() 51 | -------------------------------------------------------------------------------- /scripts/poster.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter 5 | try: 6 | from rich_argparse import ArgumentDefaultsRichHelpFormatter as ArgumentDefaultsHelpFormatter 7 | except: pass 8 | 9 | import os 10 | import re 11 | import subprocess 12 | 13 | from rich.console import Console 14 | from rich.traceback import install 15 | console=Console() 16 | if console.is_terminal: 17 | print=console.print 18 | install() 19 | 20 | TEX=r'''\documentclass{article} 21 | % Support for PDF inclusion 22 | % https://leolca.blogspot.com/2010/06/pdfposter.html 23 | \usepackage[final]{pdfpages} 24 | % Support for PDF scaling 25 | \usepackage{graphicx} 26 | \usepackage[dvips=false,pdftex=false,vtex=false]{geometry} 27 | \geometry{paperwidth=190mm, paperheight=277mm} 28 | \usepackage[cam,a4,center,pdflatex]{crop} 29 | \begin{document} 30 | % Globals: include all pages, don't auto scale 31 | \includepdf[pages=-,pagecommand={\thispagestyle{plain}}]{pages.pdf} 32 | \end{document} 33 | ''' 34 | 35 | def run(cmd): 36 | print(f'[yellow]{cmd}') 37 | subprocess.run(cmd,shell=True,check=True) 38 | 39 | def main(): 40 | parser = ArgumentParser(description="poster printer based on pdfposter and LaTeX",formatter_class=ArgumentDefaultsHelpFormatter) 41 | parser.add_argument("input", help="input PDF") 42 | parser.add_argument("output", help="output PDF", nargs='?') 43 | parser.add_argument("-s",'--scale', help="scale factor") 44 | parser.add_argument("-p",'--pages', help="output pages",default='2x2') 45 | parser.add_argument("-m",'--media', help="media size, printable area",default='190x277mm') 46 | parser.add_argument("-o",'--open', help="open resulting pdf",action='store_true') 47 | args = parser.parse_args() 48 | 49 | output=args.output or args.input.replace('.pdf','.sheets.pdf') 50 | 51 | if args.scale: 52 | opts=f'-s{args.scale}' 53 | else: 54 | m=re.match(r'(\d+)x(\d+)(.+)',args.media) 55 | x,y,u=int(m.group(1)),int(m.group(2)),m.group(3) 56 | n,m=list(map(int,args.pages.split('x'))) 57 | opts=f'-p{n*x-1}x{m*y-1}{u}' 58 | 59 | run(f'pdfposter -m{args.media} -v {opts} "{args.input}" pages.pdf') 60 | 61 | with open('sheets.tex','w') as f: f.write(TEX) 62 | run('latexmk -pdf -interaction=nonstopmode sheets.tex') 63 | os.rename('sheets.pdf',output) 64 | os.remove('pages.pdf') 65 | run('latexmk -C') 66 | if args.open: 67 | run(f'xdg-open "{output}"') 68 | 69 | 70 | if __name__=='__main__': main() 71 | -------------------------------------------------------------------------------- /scripts/requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2025.4.26 2 | charset-normalizer==3.4.2 3 | cssselect==1.3.0 4 | geojson==3.2.0 5 | idna==3.10 6 | lxml==5.4.0 7 | markdown-it-py==3.0.0 8 | mdurl==0.1.2 9 | pendulum==3.1.0 10 | pillow==11.2.1 11 | Pygments==2.19.1 12 | pyquery==2.0.1 13 | python-dateutil==2.9.0.post0 14 | requests==2.32.3 15 | rich==14.0.0 16 | rich-argparse==1.7.1 17 | six==1.17.0 18 | tzdata==2025.2 19 | urllib3==2.4.0 20 | -------------------------------------------------------------------------------- /scripts/rwsget.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | import sys 5 | import json 6 | from requests import get 7 | 8 | 9 | def main(): 10 | name = sys.argv[1] if len(sys.argv) > 1 else "waddenzee" 11 | 12 | r = get("https://www.vaarweginformatie.nl/frp/api/settings") 13 | r.raise_for_status() 14 | # print(json.dumps(r.json(), indent=2)) 15 | path = r.json()["fddDownloadPath"] 16 | 17 | r = get( 18 | "https://www.vaarweginformatie.nl/frp/api/webcontent/downloads?pageId=infra/enc" 19 | ) 20 | r.raise_for_status() 21 | # print(json.dumps(r.json(), indent=2)) 22 | 23 | data = r.json() 24 | for e in data: 25 | if e["name"].lower().startswith(name): 26 | # print(e) 27 | file_id = e["fileId"] 28 | url = f"https://www.vaarweginformatie.nl/fdd/{path}{file_id}" 29 | print(url) 30 | break 31 | 32 | 33 | main() 34 | -------------------------------------------------------------------------------- /scripts/schutzzonen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | import json 5 | import os.path 6 | 7 | import requests 8 | 9 | mv_base="https://www.nationalpark-vorpommersche-boddenlandschaft.de/" 10 | url_mv=f"{mv_base}/assets/map/navigation-755.json" 11 | 12 | mapid=56358 13 | url=f"https://umap.openstreetmap.de/de/map/nordsbefv-wassersport_{mapid}" 14 | 15 | def main(): 16 | r=requests.get(url_mv) 17 | r.raise_for_status() 18 | for e in r.json(): 19 | if e.get("label")=="Schutzzonen": 20 | for c in e["children"]: 21 | for d in c["children"]: 22 | data_url=d.get("dataUrl") 23 | if data_url: 24 | data_url=mv_base+data_url 25 | # print(data_url) 26 | # print(os.path.basename(data_url)) 27 | r=requests.get(data_url) 28 | r.raise_for_status() 29 | write(os.path.basename(data_url),r.content,"wb") 30 | 31 | r=requests.get(url) 32 | r.raise_for_status() 33 | datalayers="" 34 | for line in r.text.splitlines(): 35 | if '"datalayers": [' in line or datalayers: 36 | datalayers+=(line+"\n") if datalayers else "[" 37 | if line.strip()=="]": 38 | break 39 | # print(datalayers) 40 | datalayers=json.loads(datalayers) 41 | # print(json.dumps(datalayers,indent=2)) 42 | 43 | for l in datalayers: 44 | r=requests.get(f"https://umap.openstreetmap.de/de/datalayer/{mapid}/{l['id']}/") 45 | r.raise_for_status() 46 | # print(l["name"]) 47 | name=l["name"].replace(" ","-").replace("--","-") 48 | write(f"{name}.json",r.text) 49 | 50 | 51 | def write(filename, data,mode="w"): 52 | print(filename) 53 | with open(filename, mode) as f: 54 | f.write(data) 55 | 56 | main() -------------------------------------------------------------------------------- /tides/README.md: -------------------------------------------------------------------------------- 1 | # Tidal Atlas 2 | 3 | data sources 4 | 5 | - https://data.bsh.de/OpenData/Main/Gezeitenstrom_Kueste/Gezeitenstrom_Kueste.zip 6 | - https://data.bsh.de/OpenData/Main/Gezeitenstrom_Nordsee/Gezeitenstrom_Nordsee.zip 7 | 8 | -------------------------------------------------------------------------------- /tides/makefile: -------------------------------------------------------------------------------- 1 | SHELL=/bin/bash 2 | 3 | all: 4 | $(MAKE) seed HOUR=-6 5 | $(MAKE) seed HOUR=-5 6 | $(MAKE) seed HOUR=-4 7 | $(MAKE) seed HOUR=-3 8 | $(MAKE) seed HOUR=-2 9 | $(MAKE) seed HOUR=-1 10 | $(MAKE) seed HOUR=+0 11 | $(MAKE) seed HOUR=+1 12 | $(MAKE) seed HOUR=+2 13 | $(MAKE) seed HOUR=+3 14 | $(MAKE) seed HOUR=+4 15 | $(MAKE) seed HOUR=+5 16 | $(MAKE) seed HOUR=+6 17 | $(MAKE) seed SEEDS=figures 18 | 19 | qgis: 20 | QGIS_SERVER_PARALLEL_RENDERING=1 qgis_mapserver &>/dev/null 21 | 22 | mapproxy: mapproxy-x.yaml 23 | mapproxy-util serve-develop $< -b 0.0.0.0:8001 24 | 25 | seed: mapproxy-x.yaml 26 | echo SEEDS=$(SEEDS) HOUR=$(HOUR) 27 | mapproxy-seed -f $< -s seed.yaml --seed=$(SEEDS) 28 | 29 | HOUR=+0 30 | SEEDS=northsea,coast 31 | .PHONY: mapproxy-x.yaml 32 | mapproxy-x.yaml: 33 | cp mapproxy.yaml $@ 34 | sed 's/hw+0/hw$(HOUR)/' mapproxy-x.yaml -i 35 | sed 's/"hwhour" = 0/"hwhour" = $(HOUR)/' mapproxy-x.yaml -i 36 | 37 | clean-cache: 38 | rm -rf cache_data 39 | 40 | convert: $(patsubst cache_data/%.mbtiles,%.mbtiles,$(wildcard cache_data/*.mbtiles)) \ 41 | $(patsubst cache_data/%.mbtiles,%.sqlitedb,$(wildcard cache_data/*.mbtiles)) \ 42 | $(patsubst cache_data/%.mbtiles,%.gemf,$(wildcard cache_data/*.mbtiles)) 43 | 44 | unpack: $(patsubst cache_data/%.mbtiles,%/,$(wildcard cache_data/*.mbtiles)) 45 | 46 | %.mbtiles: cache_data/%.mbtiles 47 | ../convert.py -yfX $< $@ -t "$(basename $@) `date +%F`" -F webp -s 100 48 | 49 | %.sqlitedb: %.mbtiles 50 | ../convert.py -f $< $@ -t "$(basename $@) `date +%F`" 51 | 52 | %/: %.mbtiles 53 | ../convert.py -f $< $@ 54 | 55 | %.gemf: %.mbtiles 56 | ../data/chartconvert/convert_mbtiles.py tms $@ $< 57 | 58 | zip: 59 | zip tides.mbtiles.zip *.mbtiles 60 | zip tides.sqlitedb.zip *.sqlitedb 61 | zip tides.gemf.zip *.gemf 62 | 63 | clean: 64 | rm -frv *.mbtiles *.sqlitedb *.gemf mapproxy-x.yaml hw*/ fig/ 65 | -------------------------------------------------------------------------------- /tides/mapproxy.yaml: -------------------------------------------------------------------------------- 1 | globals: 2 | image: 3 | paletted: false 4 | resampling_method: bilinear 5 | cache: 6 | link_single_color_images: true 7 | meta_size: [ 6,6 ] 8 | meta_buffer: 200 9 | bulk_meta_tiles: true 10 | tiles: 11 | expires_hours: 24 12 | 13 | services: 14 | tms: 15 | wms: 16 | demo: 17 | 18 | sources: 19 | qgis: 20 | type: wms 21 | req: 22 | url: http://localhost:8000/ 23 | transparent: true 24 | map: tides.qgs 25 | #layers: northsea,coast 26 | filter: northsea,coast:"hwhour" = 0 27 | coverage: 28 | srs: 'EPSG:4326' 29 | bbox: [ -4.086914,48.531157,9.722900,60.168842 ] 30 | 31 | caches: 32 | tides: 33 | sources: [ "qgis:northsea,coast" ] 34 | grids: [ GLOBAL_WEBMERCATOR ] 35 | cache: 36 | type: mbtiles 37 | filename: hw+0.mbtiles 38 | figures: 39 | sources: [ "qgis:figures" ] 40 | grids: [ GLOBAL_WEBMERCATOR ] 41 | cache: 42 | type: mbtiles 43 | filename: fig.mbtiles 44 | 45 | layers: 46 | - name: tides 47 | title: "Tides DE" 48 | sources: [ tides ] 49 | - name: figures 50 | title: "Figures DE" 51 | sources: [ figures ] 52 | -------------------------------------------------------------------------------- /tides/seed.yaml: -------------------------------------------------------------------------------- 1 | seeds: 2 | northsea: 3 | caches: [ tides ] 4 | levels: [ 8,9,10 ] 5 | coverages: [ northsea ] 6 | coast: 7 | caches: [ tides ] 8 | levels: [ 11,12,13 ] 9 | coverages: [ coast ] 10 | figures: 11 | caches: [ figures ] 12 | levels: [ 10,11,12 ] 13 | coverages: [ northsea ] 14 | 15 | coverages: 16 | northsea: 17 | srs: 'EPSG:4326' 18 | bbox: [ -4.086914,48.531157,9.722900,60.168842 ] 19 | coast: 20 | srs: 'EPSG:4326' 21 | bbox: [ 6.273193,53.206033,9.404297,55.733296 ] 22 | 23 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "freenauticalchart", 3 | "version": "1.0.0", 4 | "description": "frontend for freenauticalchart.net", 5 | "scripts": { 6 | "build": "webpack build --mode production", 7 | "watch": "webpack build --mode development --watch", 8 | "serve": "webpack serve --mode development --static dist --no-client-overlay-warnings --allowed-hosts all" 9 | }, 10 | "keywords": [], 11 | "author": "quantenschaum", 12 | "license": "GPLv3", 13 | "browserslist": [ 14 | "defaults" 15 | ], 16 | "devDependencies": { 17 | "css-loader": "^7.1.2", 18 | "html-webpack-plugin": "^5.6.3", 19 | "less-loader": "^12.3.0", 20 | "mini-css-extract-plugin": "^2.9.2", 21 | "webpack": "^5.99.9", 22 | "webpack-cli": "^6.0.1", 23 | "webpack-dev-server": "^5.2.2", 24 | "workbox-webpack-plugin": "^7.3.0" 25 | }, 26 | "dependencies": { 27 | "@mapbox/leaflet-omnivore": "^0.3.4", 28 | "@maplibre/maplibre-gl-leaflet": "^0.1.1", 29 | "css-minimizer-webpack-plugin": "^7.0.2", 30 | "leaflet": "^1.9.4", 31 | "leaflet-graticule": "^0.0.1", 32 | "leaflet-hash": "^0.2.1", 33 | "leaflet.control.opacity": "^1.6.0", 34 | "leaflet.locatecontrol": "^0.84.2", 35 | "leaflet.nauticscale": "^1.1.0", 36 | "leaflet.polylinemeasure": "^3.0.0", 37 | "leaflet.tilelayer.fallback": "^1.0.4" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /web/src/grid.js: -------------------------------------------------------------------------------- 1 | import L from 'leaflet'; 2 | 3 | export function grid(dec = 0) { 4 | function d2dm(a, n) { 5 | dec = parseInt(dec); 6 | if (dec) { 7 | var n = dec; 8 | var f = Math.pow(10, n); 9 | return Math.round(a * f) / f; 10 | } 11 | var a = Math.abs(a); 12 | var d = Math.floor(a); 13 | var f = Math.pow(10, n); 14 | var m = Math.round(((60 * a) % 60) * f) / f; 15 | while (m >= 60) { 16 | m -= 60; 17 | d += 1; 18 | } 19 | var M = m == 0 ? "" : m.toFixed(n).replace(/\.?0+$/, "") + "'"; 20 | return d + '°' + M; 21 | } 22 | 23 | return L.latlngGraticule({ 24 | showLabel: true, 25 | color: '#222', 26 | zoomInterval: [ 27 | {start: 2, end: 4, interval: 30}, 28 | {start: 5, end: 5, interval: 20}, 29 | {start: 6, end: 6, interval: 10}, 30 | {start: 7, end: 7, interval: 5}, 31 | {start: 8, end: 8, interval: 2}, 32 | {start: 9, end: 9, interval: 1}, 33 | {start: 10, end: 10, interval: 30 / 60}, 34 | {start: 11, end: 11, interval: 15 / 60}, 35 | {start: 12, end: 12, interval: 10 / 60}, 36 | {start: 13, end: 13, interval: 5 / 60}, 37 | {start: 14, end: 14, interval: 2 / 60}, 38 | {start: 15, end: 15, interval: 1 / 60}, 39 | {start: 16, end: 16, interval: 1 / 60 / 2}, 40 | {start: 17, end: 17, interval: 1 / 60 / 5}, 41 | {start: 18, end: 18, interval: 1 / 60 / 10}, 42 | ], 43 | latFormatTickLabel: function (l) { 44 | let s = l < 0 ? 'S' : l > 0 ? 'N' : ''; 45 | return s + d2dm(l, 2); 46 | }, 47 | lngFormatTickLabel: function (l) { 48 | l %= 360; 49 | l -= Math.abs(l) > 180 ? Math.sign(l) * 360 : 0; 50 | let s = l < 0 ? 'W' : l > 0 ? 'E' : ''; 51 | return s + d2dm(l, 2); 52 | }, 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /web/src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantenschaum/mapping/2afcb7553ef2696d02e868faec8a1cfb847ef341/web/src/icon.png -------------------------------------------------------------------------------- /web/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | free nautical chart 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /web/src/legend.js: -------------------------------------------------------------------------------- 1 | import L from 'leaflet'; 2 | import {log} from './utils'; 3 | import './legend.less'; 4 | 5 | function llog(...args) { 6 | log('Legend', 'green', ...args); 7 | } 8 | 9 | export function legend(layerControl, options = { 10 | title: 'Legende', 11 | position: 'bottomright' 12 | }) { 13 | 14 | llog(layerControl._layers); 15 | 16 | const Legend = L.Control.extend({ 17 | onAdd: function (map) { 18 | this._map = map; 19 | llog('onAdd', this.options, map); 20 | this._container = L.DomUtil.create('div', 'legend leaflet-bar'); 21 | L.DomEvent.disableClickPropagation(this._container); 22 | this._onLayerChange = this._onLayerChange.bind(this); 23 | map.on('layeradd', this._onLayerChange); 24 | map.on('layerremove', this._onLayerChange); 25 | return this._container; 26 | }, 27 | 28 | onRemove: function () { 29 | this._map.off('layeradd', this._onLayerChange); 30 | this._map.off('layerremove', this._onLayerChange); 31 | }, 32 | 33 | _onLayerChange: function (e) { 34 | var content = ''; 35 | Object.values(layerControl._layers).forEach(e => { 36 | if (!this._map.hasLayer(e.layer)) return; 37 | const opts = e.layer.options; 38 | if (!(e.name && opts.color && opts.legend)) return; 39 | // llog('entry', e.name, opts); 40 | content += `
  • ${e.name}
  • `; 41 | }) 42 | if (content) { 43 | this._container.innerHTML = `

    ${this.options.title}

    `; 44 | } else { 45 | this._container.innerHTML = ''; 46 | } 47 | 48 | this._container.querySelectorAll('li').forEach((li) => { 49 | const text = li.getAttribute('title'); 50 | if (text) { 51 | const name = li.textContent; 52 | li.addEventListener('click', () => { 53 | alert(`${name}\n${text}`); 54 | }); 55 | } 56 | }); 57 | }, 58 | }); 59 | 60 | new Legend(options).addTo(layerControl._map); 61 | 62 | } 63 | -------------------------------------------------------------------------------- /web/src/legend.less: -------------------------------------------------------------------------------- 1 | .legend { 2 | background: white; 3 | padding: 1ex; 4 | 5 | h1 { 6 | font-size: 1.1em; 7 | margin: 0; 8 | } 9 | 10 | @media (max-width: 500px) { 11 | h1 { 12 | display: none; 13 | } 14 | } 15 | 16 | ul { 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | li { 22 | list-style-type: none; 23 | } 24 | 25 | .symbol { 26 | width: 1.3em; 27 | height: 1.3em; 28 | display: inline-block; 29 | vertical-align: middle; 30 | margin-right: 0.5em; 31 | } 32 | } 33 | 34 | .legend:empty { 35 | display: none; 36 | } 37 | -------------------------------------------------------------------------------- /web/src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "free nautical chart", 3 | "short_name": "QMAP", 4 | "description": "free nautical chart based on open data", 5 | "start_url": "/", 6 | "display": "standalone", 7 | "background_color": "#F9ECC0", 8 | "theme_color": "#3a65ff", 9 | "orientation": "any", 10 | "icons": [ 11 | { 12 | "src": "icon.png", 13 | "sizes": "192x192", 14 | "type": "image/png", 15 | "purpose": "any maskable" 16 | }, 17 | { 18 | "src": "icon.svg", 19 | "sizes": "any", 20 | "type": "image/svg+xml", 21 | "purpose": "any maskable" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /web/src/nightmode.css: -------------------------------------------------------------------------------- 1 | .night { 2 | filter: invert(100%) hue-rotate(180deg) brightness(60%); 3 | } 4 | -------------------------------------------------------------------------------- /web/src/nightmode.js: -------------------------------------------------------------------------------- 1 | import L from 'leaflet'; 2 | import {log} from './utils'; 3 | import './nightmode.css'; 4 | 5 | export const NightSwitch = L.Control.extend({ 6 | onAdd: function (map) { 7 | log('NightSwitch', 'brown', 'added'); 8 | var div = L.DomUtil.create('div', 'nightswitch leaflet-bar'); 9 | var button = L.DomUtil.create('a'); 10 | button.innerHTML = '🌙'; // 🌙 11 | button.title = 'toggle night mode'; 12 | div.appendChild(button); 13 | map = map.getContainer(); 14 | button.addEventListener('click', () => { 15 | if (map.classList.contains('night')) { 16 | map.classList.remove('night'); 17 | } else { 18 | map.classList.add('night'); 19 | } 20 | }); 21 | L.DomEvent.disableClickPropagation(div); 22 | return div; 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /web/src/store.js: -------------------------------------------------------------------------------- 1 | import {log} from './utils'; 2 | 3 | const isStandalone = window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone; 4 | 5 | function xlog(...args) { 6 | log('Restore', 'blue', ...args); 7 | } 8 | 9 | export function restoreLayers(control, list) { 10 | 11 | const map = control._map; 12 | const storage = isStandalone ? localStorage : sessionStorage; 13 | 14 | function restoreActiveLayers() { 15 | var active = storage.getItem("activeLayers"); 16 | active = JSON.parse(active); 17 | if (list) active = list.split(','); 18 | if (!active) return; 19 | xlog("restore", active); 20 | for (let i = 0; i < control._layers.length; i++) { 21 | let l = control._layers[i]; 22 | if (active.includes(l.name)) { 23 | map.addLayer(l.layer); 24 | } else { 25 | map.removeLayer(l.layer); 26 | } 27 | } 28 | } 29 | 30 | restoreActiveLayers(); 31 | 32 | function storeActiveLayers() { 33 | var active = []; 34 | for (let i = 0; i < control._layers.length; i++) { 35 | let l = control._layers[i]; 36 | // console.log(i,l,map.hasLayer(l.layer)); 37 | if (map.hasLayer(l.layer)) { 38 | active.push(l.name); 39 | } 40 | } 41 | // xlog("store", active); 42 | storage.setItem("activeLayers", JSON.stringify(active)); 43 | } 44 | 45 | storeActiveLayers(); 46 | 47 | map.on('layeradd layerremove overlayadd overlayremove', storeActiveLayers); 48 | } 49 | -------------------------------------------------------------------------------- /web/src/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | html, body, #map { 7 | width: 100%; 8 | height: 100%; 9 | } 10 | 11 | .grayscale { 12 | filter: grayscale(100%); 13 | } 14 | 15 | .invert { 16 | filter: invert(100%); 17 | } 18 | 19 | .highlight { 20 | /*color: red !important;*/ 21 | font-weight: bold; 22 | /*animation-name: blink;*/ 23 | /*animation-timing-function: ease-in;*/ 24 | /*animation-duration: 1s;*/ 25 | /*animation-iteration-count: 3;*/ 26 | /*animation-fill-mode: both;*/ 27 | } 28 | 29 | @keyframes blink { 30 | 0%, 100% { 31 | } 32 | 50% { 33 | background-color: blue; 34 | } 35 | } 36 | 37 | @media (max-width: 600px) { 38 | .leaflet-control-attribution { 39 | max-width: 70%; 40 | } 41 | 42 | .control_container { 43 | transform: rotate(90deg); 44 | transform-origin: 100% 0%; 45 | display: none; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /web/src/utils.js: -------------------------------------------------------------------------------- 1 | const debug = process.env.NODE_ENV === 'development'; 2 | 3 | export function log(label, color, ...args) { 4 | if (!debug) return; 5 | console.log('%c' + label, 6 | `background-color: ${color}; color: white; font-weight: bold; padding: 4px 8px; border-radius: 4px`, 7 | ...args); 8 | } 9 | -------------------------------------------------------------------------------- /web/src/vector.js: -------------------------------------------------------------------------------- 1 | import L from 'leaflet'; 2 | import {log} from './utils'; 3 | 4 | function xlog(...args) { 5 | log('Vector', 'magenta', ...args); 6 | } 7 | 8 | export async function addVectorLayer(control, name, url, opts = {color: 'blue'}) { 9 | xlog('add', name, url); 10 | await fetch(url) 11 | .then(response => response.json()) 12 | .then(json => { 13 | // xlog('json', json); 14 | const layer = L.geoJSON(json, { 15 | ...opts, 16 | onEachFeature: (f, l) => { 17 | if (f.properties && f.properties.name) { 18 | l.bindPopup(f.properties.name); 19 | } 20 | }, 21 | style: f => { 22 | if (f.geometry.type == 'Point') return {}; 23 | return {weight: 4, opacity: 0.8, color: opts.color, fillOpacity: 0.1}; 24 | }, 25 | pointToLayer: (f, latlng) => 26 | L.circleMarker(latlng, { 27 | radius: 6, 28 | fillColor: opts.color, 29 | weight: 0, 30 | fillOpacity: 1, 31 | }), 32 | }); 33 | // xlog('add', name, layer); 34 | control.addOverlay(layer, name); 35 | if (opts.active) layer.addTo(control._map); 36 | }); 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /www/charttools.css: -------------------------------------------------------------------------------- 1 | .charttools-button { 2 | width: 33px; 3 | background-color: white; 4 | border: 1px solid gray; 5 | } 6 | .charttools-label { 7 | font-family: monospace; 8 | background-color: rgba(255,255,255,0.8); 9 | width: fit-content !important; 10 | height: fit-content !important; 11 | text-align: center; 12 | } 13 | .charttools-info { 14 | font-family: monospace; 15 | background-color: rgba(255,255,255,0.8); 16 | width: fit-content !important; 17 | height: fit-content !important; 18 | } 19 | -------------------------------------------------------------------------------- /www/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantenschaum/mapping/2afcb7553ef2696d02e868faec8a1cfb847ef341/www/favicon.ico -------------------------------------------------------------------------------- /www/map.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | QMAP 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 41 | 64 | 65 | 66 |
    67 | 68 | 69 | -------------------------------------------------------------------------------- /www/vector.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MapLibre 6 | 7 | 8 | 9 | 10 | 11 | 21 | 22 | 23 |
    24 | 25 | 26 | -------------------------------------------------------------------------------- /www/vector.js: -------------------------------------------------------------------------------- 1 | var style=(new URL(document.location)).searchParams.get("style"); 2 | if(!style) style='style.json'; 3 | 4 | async function mergeStyles(styleUrl1, styleUrl2) { 5 | const [style1, style2] = await Promise.all([ 6 | fetch(styleUrl1).then(res => res.json()), 7 | fetch(styleUrl2).then(res => res.json()) 8 | ]); 9 | 10 | const mergedSources = { 11 | ...style1.sources, 12 | ...style2.sources 13 | }; 14 | 15 | const mergedLayers = [...style1.layers, ...style2.layers]; 16 | 17 | const mergedStyle = { 18 | ...style1, 19 | sprite: style2.sprite, 20 | sources: mergedSources, 21 | layers: mergedLayers 22 | }; 23 | 24 | return mergedStyle; 25 | } 26 | 27 | mergeStyles('https://api.maptiler.com/maps/openstreetmap/style.json?key=L8FrrrJGE2n415wJo8BL', style) 28 | .then(mergedStyle => { 29 | const map = new maplibregl.Map({ 30 | container: 'map', 31 | style: mergedStyle, 32 | center: [9.239,54.397], // starting position [lng, lat] 33 | zoom: 7, // starting zoom 34 | hash: true, 35 | }); 36 | map.addControl(new maplibregl.NavigationControl(),'top-left'); 37 | }); 38 | --------------------------------------------------------------------------------