├── .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 | 
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 | 
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 |
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 | 
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 | 
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 | 
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 | 
10 |
11 | Raster Overlay
12 |
13 | 
14 |
15 | ## AvNav
16 |
17 | Raster Chart
18 |
19 | 
20 |
21 | Vector Charts via Ocharts(NG)
22 |
23 | 
24 |
25 | ## OpenCPN
26 |
27 | Vectop Chart
28 |
29 | 
30 |
31 | ## JOSM
32 |
33 | Imagery Layer
34 |
35 | 
36 |
37 | ## Garmin
38 |
39 | Small handheld device eTrex Vista HCx
40 |
41 | 
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 | 
9 |
10 | 
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 | 
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 |
69 |
--------------------------------------------------------------------------------
/icons/2_cones_down.svg:
--------------------------------------------------------------------------------
1 |
2 |
69 |
--------------------------------------------------------------------------------
/icons/2_cones_point_together.svg:
--------------------------------------------------------------------------------
1 |
2 |
68 |
--------------------------------------------------------------------------------
/icons/2_cones_up.svg:
--------------------------------------------------------------------------------
1 |
2 |
68 |
--------------------------------------------------------------------------------
/icons/2_spheres.svg:
--------------------------------------------------------------------------------
1 |
2 |
77 |
--------------------------------------------------------------------------------
/icons/TSS-arrow-dashed.svg:
--------------------------------------------------------------------------------
1 |
2 |
46 |
--------------------------------------------------------------------------------
/icons/TSS-arrow.svg:
--------------------------------------------------------------------------------
1 |
2 |
46 |
--------------------------------------------------------------------------------
/icons/VHF.svg:
--------------------------------------------------------------------------------
1 |
2 |
33 |
--------------------------------------------------------------------------------
/icons/arc-0-G.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
69 |
--------------------------------------------------------------------------------
/icons/arc-0-R.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
69 |
--------------------------------------------------------------------------------
/icons/arc-0-W.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
69 |
--------------------------------------------------------------------------------
/icons/arc-0-Y.svg:
--------------------------------------------------------------------------------
1 | arc-0-W.svg
--------------------------------------------------------------------------------
/icons/arc-1-G.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
69 |
--------------------------------------------------------------------------------
/icons/arc-1-R.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
69 |
--------------------------------------------------------------------------------
/icons/arc-1-W.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
69 |
--------------------------------------------------------------------------------
/icons/arc-1-Y.svg:
--------------------------------------------------------------------------------
1 | arc-1-W.svg
--------------------------------------------------------------------------------
/icons/arc-2-G.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
69 |
--------------------------------------------------------------------------------
/icons/arc-2-R.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
69 |
--------------------------------------------------------------------------------
/icons/arc-2-W.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
69 |
--------------------------------------------------------------------------------
/icons/arc-2-Y.svg:
--------------------------------------------------------------------------------
1 | arc-2-W.svg
--------------------------------------------------------------------------------
/icons/arc-3-G.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
69 |
--------------------------------------------------------------------------------
/icons/arc-3-R.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
69 |
--------------------------------------------------------------------------------
/icons/arc-3-W.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
69 |
--------------------------------------------------------------------------------
/icons/arc-3-Y.svg:
--------------------------------------------------------------------------------
1 | arc-3-W.svg
--------------------------------------------------------------------------------
/icons/arc-4-G.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
69 |
--------------------------------------------------------------------------------
/icons/arc-4-R.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
69 |
--------------------------------------------------------------------------------
/icons/arc-4-W.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
69 |
--------------------------------------------------------------------------------
/icons/arc-4-Y.svg:
--------------------------------------------------------------------------------
1 | arc-4-W.svg
--------------------------------------------------------------------------------
/icons/area-g.svg:
--------------------------------------------------------------------------------
1 |
2 |
66 |
--------------------------------------------------------------------------------
/icons/area-m.svg:
--------------------------------------------------------------------------------
1 |
2 |
65 |
--------------------------------------------------------------------------------
/icons/bird.svg:
--------------------------------------------------------------------------------
1 |
2 |
63 |
--------------------------------------------------------------------------------
/icons/cable.svg:
--------------------------------------------------------------------------------
1 |
2 |
60 |
--------------------------------------------------------------------------------
/icons/cairn-lmk.svg:
--------------------------------------------------------------------------------
1 |
2 |
89 |
--------------------------------------------------------------------------------
/icons/cairn.svg:
--------------------------------------------------------------------------------
1 |
2 |
90 |
--------------------------------------------------------------------------------
/icons/chimney.svg:
--------------------------------------------------------------------------------
1 |
2 |
59 |
--------------------------------------------------------------------------------
/icons/clearance-safe.svg:
--------------------------------------------------------------------------------
1 |
2 |
48 |
--------------------------------------------------------------------------------
/icons/clearance.svg:
--------------------------------------------------------------------------------
1 |
2 |
46 |
--------------------------------------------------------------------------------
/icons/cliff.svg:
--------------------------------------------------------------------------------
1 |
2 |
56 |
--------------------------------------------------------------------------------
/icons/cone_point_down.svg:
--------------------------------------------------------------------------------
1 |
2 |
56 |
--------------------------------------------------------------------------------
/icons/cone_point_up.svg:
--------------------------------------------------------------------------------
1 |
2 |
56 |
--------------------------------------------------------------------------------
/icons/cross.svg:
--------------------------------------------------------------------------------
1 |
2 |
68 |
--------------------------------------------------------------------------------
/icons/customs-limit.svg:
--------------------------------------------------------------------------------
1 |
2 |
42 |
--------------------------------------------------------------------------------
/icons/cylinder.svg:
--------------------------------------------------------------------------------
1 |
2 |
68 |
--------------------------------------------------------------------------------
/icons/dir-buoyage-a.svg:
--------------------------------------------------------------------------------
1 |
2 |
32 |
--------------------------------------------------------------------------------
/icons/dir-buoyage-b.svg:
--------------------------------------------------------------------------------
1 |
2 |
36 |
--------------------------------------------------------------------------------
/icons/eddies.svg:
--------------------------------------------------------------------------------
1 |
2 |
59 |
--------------------------------------------------------------------------------
/icons/ferry.svg:
--------------------------------------------------------------------------------
1 |
2 |
23 |
--------------------------------------------------------------------------------
/icons/fish.svg:
--------------------------------------------------------------------------------
1 |
2 |
71 |
--------------------------------------------------------------------------------
/icons/flash.svg:
--------------------------------------------------------------------------------
1 |
2 |
59 |
--------------------------------------------------------------------------------
/icons/lattice.svg:
--------------------------------------------------------------------------------
1 |
2 |
76 |
--------------------------------------------------------------------------------
/icons/light-major.svg:
--------------------------------------------------------------------------------
1 |
2 |
24 |
--------------------------------------------------------------------------------
/icons/light-minor.svg:
--------------------------------------------------------------------------------
1 |
2 |
18 |
--------------------------------------------------------------------------------
/icons/light_float.svg:
--------------------------------------------------------------------------------
1 |
2 |
64 |
--------------------------------------------------------------------------------
/icons/line-dashed.svg:
--------------------------------------------------------------------------------
1 |
2 |
39 |
--------------------------------------------------------------------------------
/icons/line-solid.svg:
--------------------------------------------------------------------------------
1 |
2 |
39 |
--------------------------------------------------------------------------------
/icons/no-entry.svg:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/icons/notice-board.svg:
--------------------------------------------------------------------------------
1 |
2 |
45 |
--------------------------------------------------------------------------------
/icons/obstruction.svg:
--------------------------------------------------------------------------------
1 |
2 |
61 |
--------------------------------------------------------------------------------
/icons/perch-port.svg:
--------------------------------------------------------------------------------
1 |
2 |
55 |
--------------------------------------------------------------------------------
/icons/perch-starboard.svg:
--------------------------------------------------------------------------------
1 |
2 |
55 |
--------------------------------------------------------------------------------
/icons/perch.svg:
--------------------------------------------------------------------------------
1 |
2 |
64 |
--------------------------------------------------------------------------------
/icons/pilot-boarding.svg:
--------------------------------------------------------------------------------
1 |
2 |
34 |
--------------------------------------------------------------------------------
/icons/pipeline.svg:
--------------------------------------------------------------------------------
1 |
2 |
56 |
--------------------------------------------------------------------------------
/icons/platform.svg:
--------------------------------------------------------------------------------
1 |
2 |
42 |
--------------------------------------------------------------------------------
/icons/pole.svg:
--------------------------------------------------------------------------------
1 |
2 |
64 |
--------------------------------------------------------------------------------
/icons/post.svg:
--------------------------------------------------------------------------------
1 |
2 |
64 |
--------------------------------------------------------------------------------
/icons/pylon.svg:
--------------------------------------------------------------------------------
1 |
2 |
39 |
--------------------------------------------------------------------------------
/icons/radar-reflector.svg:
--------------------------------------------------------------------------------
1 |
2 |
65 |
--------------------------------------------------------------------------------
/icons/rescue.svg:
--------------------------------------------------------------------------------
1 |
2 |
33 |
--------------------------------------------------------------------------------
/icons/rock-covers.svg:
--------------------------------------------------------------------------------
1 |
2 |
68 |
--------------------------------------------------------------------------------
/icons/rock-dangerous.svg:
--------------------------------------------------------------------------------
1 |
2 |
60 |
--------------------------------------------------------------------------------
/icons/rock-submerged.svg:
--------------------------------------------------------------------------------
1 |
2 |
28 |
--------------------------------------------------------------------------------
/icons/sandwaves.svg:
--------------------------------------------------------------------------------
1 |
2 |
34 |
--------------------------------------------------------------------------------
/icons/seal.svg:
--------------------------------------------------------------------------------
1 |
2 |
21 |
--------------------------------------------------------------------------------
/icons/shellfish.svg:
--------------------------------------------------------------------------------
1 |
2 |
55 |
--------------------------------------------------------------------------------
/icons/sphere.svg:
--------------------------------------------------------------------------------
1 |
2 |
64 |
--------------------------------------------------------------------------------
/icons/sphere_over_rhombus.svg:
--------------------------------------------------------------------------------
1 |
2 |
89 |
--------------------------------------------------------------------------------
/icons/spire.svg:
--------------------------------------------------------------------------------
1 | church.svg
--------------------------------------------------------------------------------
/icons/stake.svg:
--------------------------------------------------------------------------------
1 |
2 |
64 |
--------------------------------------------------------------------------------
/icons/triangle_point_down.svg:
--------------------------------------------------------------------------------
1 |
2 |
70 |
--------------------------------------------------------------------------------
/icons/triangle_point_up.svg:
--------------------------------------------------------------------------------
1 |
2 |
70 |
--------------------------------------------------------------------------------
/icons/watertower.svg:
--------------------------------------------------------------------------------
1 |
2 |
44 |
--------------------------------------------------------------------------------
/icons/windmill.svg:
--------------------------------------------------------------------------------
1 |
2 |
38 |
--------------------------------------------------------------------------------
/icons/wiredrag.svg:
--------------------------------------------------------------------------------
1 |
2 |
48 |
--------------------------------------------------------------------------------
/icons/withy-port.svg:
--------------------------------------------------------------------------------
1 |
2 |
59 |
--------------------------------------------------------------------------------
/icons/withy-starboard.svg:
--------------------------------------------------------------------------------
1 |
2 |
59 |
--------------------------------------------------------------------------------
/icons/wreck-hull_showing.svg:
--------------------------------------------------------------------------------
1 |
2 |
59 |
--------------------------------------------------------------------------------
/icons/wreck-non-dangerous.svg:
--------------------------------------------------------------------------------
1 |
2 |
46 |
--------------------------------------------------------------------------------
/icons/x-shape.svg:
--------------------------------------------------------------------------------
1 |
2 |
66 |
--------------------------------------------------------------------------------
/icons/yacht-berth.svg:
--------------------------------------------------------------------------------
1 |
2 |
50 |
--------------------------------------------------------------------------------
/icons/yacht-club.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
--------------------------------------------------------------------------------