├── .clang-tidy ├── .github ├── FUNDING.yml ├── actions │ ├── build │ │ └── action.yml │ ├── cmake │ │ └── action.yml │ ├── ctest │ │ └── action.yml │ ├── install-from-git │ │ └── action.yml │ ├── install-macos │ │ └── action.yml │ └── install-ubuntu │ │ └── action.yml └── workflows │ ├── ci.yml │ └── clang-tidy.yml ├── .gitignore ├── CHANGELOG.md ├── CMakeLists.txt ├── COPYING ├── README.md ├── cmake ├── FindLZ4.cmake ├── FindOsmium.cmake └── FindProtozero.cmake ├── coastline.map ├── coastline_sources.qgs ├── coastline_sqlite.qgs ├── doc ├── CMakeLists.txt ├── Doxyfile.in └── header.html ├── include └── gdalcpp.hpp ├── man ├── manpage.template ├── osmcoastline.md ├── osmcoastline_filter.md ├── osmcoastline_readmeta.md ├── osmcoastline_segments.md └── osmcoastline_ways.md ├── osmcoastline_readmeta ├── render_image.sh ├── runtest.sh.in ├── simplify_and_split_postgis ├── README ├── create_water_polygons.sql ├── setup_bbox_tiles.sql ├── setup_tables.sql ├── simplify_land_polygons.sql ├── split_land_polygons.sql └── split_tiles.sql ├── simplify_and_split_spatialite ├── create_grid_3857.sql ├── create_grid_4326.sql └── simplify.sql ├── src ├── CMakeLists.txt ├── coastline_polygons.cpp ├── coastline_polygons.hpp ├── coastline_ring.cpp ├── coastline_ring.hpp ├── coastline_ring_collection.cpp ├── coastline_ring_collection.hpp ├── nodegrid2opl.cpp ├── options.cpp ├── options.hpp ├── osmcoastline.cpp ├── osmcoastline_filter.cpp ├── osmcoastline_segments.cpp ├── osmcoastline_ways.cpp ├── output_database.cpp ├── output_database.hpp ├── return_codes.hpp ├── srs.cpp ├── srs.hpp ├── stats.hpp ├── util.hpp ├── version.cpp.in └── version.hpp ├── taginfo.json ├── test ├── CMakeLists.txt ├── init.sh └── t │ ├── gdal-driver-gpkg.sh │ ├── gdal-driver-shapefile.sh │ ├── invalid-complex-overlap.sh │ ├── invalid-direction.sh │ ├── invalid-duplicate-segments-1.sh │ ├── invalid-duplicate-segments-2.sh │ ├── invalid-node-id-mismatch.sh │ ├── invalid-node-with-coastline-tag.sh │ ├── invalid-part-reversed.sh │ ├── invalid-ring-not-closed.sh │ ├── invalid-self-intersection-on-closed-ring-one-way.sh │ ├── invalid-self-intersection-on-closed-ring-two-ways.sh │ ├── invalid-self-intersection-on-open-ring.sh │ ├── overlapping-islands.sh │ ├── usage-and-help.sh │ ├── valid-antimeridian.sh │ ├── valid-inland-sea-with-island.sh │ ├── valid-inland-sea.sh │ ├── valid-island-from-one-way.sh │ ├── valid-island-from-two-ways.sh │ └── valid-two-small-islands.sh └── testdata.osm /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: '*,-altera-*,-android-cloexec-*,-boost-use-ranges,-bugprone-easily-swappable-parameters,-cert-err58-cpp,-cert-err60-cpp,-cppcoreguidelines-avoid-c-arrays,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-cppcoreguidelines-pro-type-static-cast-downcast,-cppcoreguidelines-pro-type-vararg,-fuchsia-*,-google-runtime-references,-hicpp-avoid-c-arrays,-hicpp-no-array-decay,-hicpp-vararg,-llvmlibc-*,-misc-no-recursion,-modernize-avoid-c-arrays,-modernize-use-trailing-return-type,-readability-implicit-bool-cast,-readability-identifier-length,-readability-implicit-bool-conversion,-readability-magic-numbers,-readability-use-anyofallof' 3 | # 4 | # For a list of check options, see: 5 | # http://clang.llvm.org/extra/clang-tidy/checks/list.html 6 | # 7 | # Disabled checks: 8 | # 9 | # altera-* 10 | # Not applicable. 11 | # 12 | # android-cloexec-* 13 | # O_CLOEXEC isn't available on Windows making this non-portable. 14 | # 15 | # boost-use-ranges 16 | # Would introduce extra dependency on boost. 17 | # 18 | # bugprone-easily-swappable-parameters 19 | # When you need them, you need them. 20 | # 21 | # cert-err58-cpp 22 | # Rather unlikely that this is a problem. 23 | # 24 | # cert-err60-cpp 25 | # Reports exceptions from standard library as broken. 26 | # 27 | # cppcoreguidelines-avoid-c-arrays 28 | # hicpp-avoid-c-arrays 29 | # modernize-avoid-c-arrays 30 | # Makes sense for some array, but especially for char arrays using 31 | # std::array isn't a good solution. 32 | # 33 | # cppcoreguidelines-avoid-magic-numbers 34 | # readability-magic-numbers 35 | # Generally good advice, but there are too many places where this is 36 | # useful, for instance in tests. 37 | # 38 | # cppcoreguidelines-pro-bounds-array-to-pointer-decay 39 | # Limited use and many false positives including for all asserts. 40 | # 41 | # cppcoreguidelines-pro-bounds-pointer-arithmetic 42 | # Difficult to get by without it... 43 | # 44 | # cppcoreguidelines-pro-type-static-cast-downcast 45 | # This is needed and totally okay if we are sure about the types. 46 | # 47 | # cppcoreguidelines-pro-type-vararg 48 | # We need some of these functions at least and for some functions it isn't 49 | # even clear that those are vararg functions. 50 | # 51 | # fuchsia-* 52 | # Much too strict. 53 | # 54 | # google-runtime-references 55 | # This is just a matter of preference. 56 | # 57 | # hicpp-no-array-decay 58 | # Alias for cppcoreguidelines-pro-bounds-array-to-pointer-decay. 59 | # 60 | # hicpp-vararg 61 | # Too strict, sometimes calling vararg functions is necessary. 62 | # 63 | # llvmlibc-* 64 | # Not applicable. 65 | # 66 | # misc-no-recursion 67 | # Nothing wrong about recursion. 68 | # 69 | # modernize-use-trailing-return-type 70 | # I am not quite that modern. 71 | # 72 | # readability-identifier-length 73 | # Sometimes short variable names are okay. 74 | # 75 | # readability-implicit-bool-cast 76 | # Old name for readability-implicit-bool-conversion. 77 | # 78 | # readability-implicit-bool-conversion 79 | # I don't think this makes the code more readable. 80 | # 81 | # readability-use-anyofallof 82 | # Not necessarily more readable. 83 | # 84 | #WarningsAsErrors: '*' 85 | ... 86 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: "https://osmcode.org/sponsors.html" 2 | -------------------------------------------------------------------------------- /.github/actions/build/action.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | runs: 4 | using: composite 5 | steps: 6 | - name: Build 7 | run: make VERBOSE=1 8 | shell: bash 9 | working-directory: build 10 | 11 | -------------------------------------------------------------------------------- /.github/actions/cmake/action.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | runs: 4 | using: composite 5 | steps: 6 | - name: Create build directory 7 | run: mkdir build 8 | shell: bash 9 | - name: Configure 10 | run: | 11 | cmake -LA .. \ 12 | -DCMAKE_BUILD_TYPE=${BUILD_TYPE} 13 | shell: bash 14 | working-directory: build 15 | -------------------------------------------------------------------------------- /.github/actions/ctest/action.yml: -------------------------------------------------------------------------------- 1 | name: ctest 2 | 3 | runs: 4 | using: composite 5 | steps: 6 | - name: Test 7 | run: ctest --output-on-failure 8 | shell: bash 9 | working-directory: build 10 | -------------------------------------------------------------------------------- /.github/actions/install-from-git/action.yml: -------------------------------------------------------------------------------- 1 | name: Install Prerequisites from git 2 | 3 | runs: 4 | using: composite 5 | steps: 6 | - name: Install from git 7 | run: | 8 | git clone --quiet --depth 1 https://github.com/osmcode/libosmium.git ../libosmium 9 | git clone --quiet --depth 1 https://github.com/mapbox/protozero.git ../protozero 10 | shell: bash 11 | -------------------------------------------------------------------------------- /.github/actions/install-macos/action.yml: -------------------------------------------------------------------------------- 1 | name: Install homebrew packages on macOS 2 | 3 | runs: 4 | using: composite 5 | steps: 6 | - name: Install homebrew packages 7 | run: | 8 | brew install \ 9 | gdal \ 10 | spatialite-tools 11 | shell: bash 12 | -------------------------------------------------------------------------------- /.github/actions/install-ubuntu/action.yml: -------------------------------------------------------------------------------- 1 | name: Install apt packages on Ubuntu/Debian 2 | 3 | runs: 4 | using: composite 5 | steps: 6 | - name: Install packages 7 | run: | 8 | sudo apt-get update -qq 9 | sudo apt-get install -yq \ 10 | libbz2-dev \ 11 | libgdal-dev \ 12 | libgeos-dev \ 13 | pandoc \ 14 | spatialite-bin 15 | shell: bash 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | linux: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | name: [Ubuntu-20, Ubuntu-22, Ubuntu-24, Debian-10, Debian-11, Debian-12, Debian-Testing, Debian-Experimental, Fedora-37, Fedora-38, Fedora-39, Fedora-40] 12 | build_type: [Dev] 13 | cpp_compiler: [g++] 14 | cpp_version: [c++14] 15 | include: 16 | - name: Ubuntu-20 17 | # Uses gcc 9.3.0, clang 10.0.0, cmake 3.16.3 18 | image: "ubuntu:20.04" 19 | ubuntu: 20 20 | - name: Ubuntu-22 21 | # Uses gcc 12.2.0, clang 15.0.7, cmake 3.24.2 22 | image: "ubuntu:22.04" 23 | ubuntu: 22 24 | CXXFLAGS: -Wno-stringop-overread 25 | - name: Ubuntu-24 26 | image: "ubuntu:24.04" 27 | ubuntu: 24 28 | CXXFLAGS: -Wno-stringop-overread 29 | - name: Debian-10 30 | # Uses gcc 8.3.0, clang 7.0.1, cmake 3.13.4 31 | image: "debian:buster" 32 | - name: Debian-11 33 | # Uses gcc 10.2.1, clang 11.0.1, cmake 3.18.4 34 | image: "debian:bullseye" 35 | - name: Debian-11 36 | image: "debian:bullseye" 37 | cpp_version: c++17 38 | - name: Debian-11 39 | image: "debian:bullseye" 40 | cpp_version: c++20 41 | - name: Debian-11 42 | image: "debian:bullseye" 43 | c_compiler: clang 44 | cpp_compiler: clang++ 45 | - name: Debian-11 46 | image: "debian:bullseye" 47 | c_compiler: clang 48 | cpp_compiler: clang++ 49 | cpp_version: c++17 50 | - name: Debian-11 51 | image: "debian:bullseye" 52 | c_compiler: clang 53 | cpp_compiler: clang++ 54 | cpp_version: c++20 55 | - name: Debian-11 56 | image: "debian:bullseye" 57 | build_type: RelWithDebInfo 58 | - name: Debian-11 59 | image: "debian:bullseye" 60 | c_compiler: clang 61 | cpp_compiler: clang++ 62 | CXXFLAGS: -fsanitize=address,undefined,integer -fno-sanitize-recover=all -fno-omit-frame-pointer 63 | LDFLAGS: -fsanitize=address,undefined,integer 64 | - name: Debian-12 65 | # Uses gcc 12.2.0, clang 15.0.6, cmake 3.25.1 66 | image: "debian:bookworm" 67 | CXXFLAGS: -Wno-stringop-overread 68 | - name: Debian-12 69 | image: "debian:bookworm" 70 | cpp_version: c++17 71 | CXXFLAGS: -Wno-stringop-overread 72 | - name: Debian-12 73 | image: "debian:bookworm" 74 | cpp_version: c++20 75 | CXXFLAGS: -Wno-stringop-overread 76 | - name: Debian-12 77 | image: "debian:bookworm" 78 | c_compiler: clang 79 | cpp_compiler: clang++ 80 | - name: Debian-12 81 | image: "debian:bookworm" 82 | c_compiler: clang 83 | cpp_compiler: clang++ 84 | cpp_version: c++17 85 | - name: Debian-12 86 | image: "debian:bookworm" 87 | c_compiler: clang 88 | cpp_compiler: clang++ 89 | cpp_version: c++20 90 | - name: Debian-12 91 | image: "debian:bookworm" 92 | build_type: RelWithDebInfo 93 | CXXFLAGS: -Wno-stringop-overread 94 | - name: Debian-12 95 | image: "debian:bookworm" 96 | c_compiler: clang 97 | cpp_compiler: clang++ 98 | CXXFLAGS: -fsanitize=address,undefined,integer -fno-sanitize-recover=all -fno-omit-frame-pointer 99 | LDFLAGS: -fsanitize=address,undefined,integer 100 | - name: Debian-Testing 101 | image: "debian:testing" 102 | CXXFLAGS: -Wno-stringop-overread 103 | - name: Debian-Testing 104 | image: "debian:testing" 105 | c_compiler: clang 106 | cpp_compiler: clang++ 107 | - name: Debian-Experimental 108 | image: "debian:experimental" 109 | CXXFLAGS: -Wno-stringop-overread 110 | - name: Debian-Experimental 111 | image: "debian:experimental" 112 | c_compiler: clang 113 | cpp_compiler: clang++ 114 | - name: Fedora-37 115 | # Uses gcc 12.3.1, clang 15.0.7, cmake 3.26.4 116 | image: "fedora:37" 117 | CXXFLAGS: -Wno-stringop-overread 118 | - name: Fedora-38 119 | # Uses gcc 13.0.1, clang 16.0.5, cmake 3.26.4 120 | image: "fedora:38" 121 | CXXFLAGS: -Wno-stringop-overread 122 | - name: Fedora-39 123 | image: "fedora:39" 124 | CXXFLAGS: -Wno-stringop-overread 125 | - name: Fedora-40 126 | image: "fedora:40" 127 | CXXFLAGS: -Wno-stringop-overread 128 | container: 129 | image: ${{ matrix.image }} 130 | env: 131 | LANG: en_US.UTF-8 132 | BUILD_TYPE: ${{ matrix.build_type }} 133 | CC: ${{ matrix.c_compiler }} 134 | CXX: ${{ matrix.cpp_compiler }} 135 | CXXFLAGS: -Werror ${{ matrix.CXXFLAGS }} 136 | LDFLAGS: ${{ matrix.LDFLAGS }} 137 | CPP_VERSION: ${{ matrix.cpp_version }} 138 | WITH_PROJ: ON 139 | APT_LISTCHANGES_FRONTEND: none 140 | DEBIAN_FRONTEND: noninteractive 141 | steps: 142 | - name: Prepare container (apt) 143 | shell: bash 144 | if: startsWith(matrix.image, 'debian:') || startsWith(matrix.image, 'ubuntu:') 145 | run: | 146 | apt-get update -qq 147 | apt-get install -yq \ 148 | clang \ 149 | cmake \ 150 | g++ \ 151 | git \ 152 | libbz2-dev \ 153 | libexpat1-dev \ 154 | libgdal-dev \ 155 | libgeos-dev \ 156 | liblz4-dev \ 157 | make \ 158 | spatialite-bin \ 159 | zlib1g-dev 160 | - name: Install compiler 161 | shell: bash 162 | if: matrix.cpp_compiler == 'clang++-14' 163 | run: apt-get install -yq clang-14 164 | - name: Prepare container (dnf) 165 | shell: bash 166 | if: startsWith(matrix.image, 'fedora:') 167 | run: | 168 | dnf install --quiet --assumeyes \ 169 | bzip2-devel \ 170 | cmake \ 171 | expat-devel \ 172 | gcc-c++ \ 173 | gdal-devel \ 174 | geos-devel \ 175 | git \ 176 | lz4-devel \ 177 | make \ 178 | proj-devel \ 179 | spatialite-tools \ 180 | zlib-devel 181 | - uses: actions/checkout@v4 182 | - uses: ./.github/actions/install-from-git 183 | - uses: ./.github/actions/cmake 184 | - uses: ./.github/actions/build 185 | - uses: ./.github/actions/ctest 186 | 187 | ubuntu-latest: 188 | runs-on: ubuntu-24.04 189 | env: 190 | CC: clang-18 191 | CXX: clang++-18 192 | BUILD_TYPE: Dev 193 | steps: 194 | - uses: actions/checkout@v4 195 | - uses: ./.github/actions/install-ubuntu 196 | - uses: ./.github/actions/install-from-git 197 | - uses: ./.github/actions/cmake 198 | - uses: ./.github/actions/build 199 | - uses: ./.github/actions/ctest 200 | 201 | macos: 202 | strategy: 203 | fail-fast: false 204 | matrix: 205 | os: 206 | - "macos-14" 207 | - "macos-15" 208 | build_type: [Dev] 209 | include: 210 | - os: "macos-14" 211 | build_type: Release 212 | runs-on: ${{ matrix.os }} 213 | env: 214 | CC: clang 215 | CXX: clang++ 216 | BUILD_TYPE: ${{ matrix.build_type }} 217 | steps: 218 | - uses: actions/checkout@v4 219 | - uses: ./.github/actions/install-macos 220 | - uses: ./.github/actions/install-from-git 221 | - uses: ./.github/actions/cmake 222 | - uses: ./.github/actions/build 223 | - uses: ./.github/actions/ctest 224 | -------------------------------------------------------------------------------- /.github/workflows/clang-tidy.yml: -------------------------------------------------------------------------------- 1 | name: clang-tidy 2 | 3 | on: workflow_dispatch 4 | 5 | jobs: 6 | clang-tidy: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | image: ["debian:bookworm", "debian:testing", "debian:experimental"] 12 | include: 13 | - image: "debian:bookworm" 14 | clang: 15 15 | - image: "debian:testing" 16 | clang: 19 17 | - image: "debian:experimental" 18 | clang: 20 19 | container: 20 | image: ${{ matrix.image }} 21 | env: 22 | BUILD_TYPE: Dev 23 | CC: clang-${{ matrix.clang }} 24 | CXX: clang++-${{ matrix.clang }} 25 | CPP_VERSION: c++14 26 | APT_LISTCHANGES_FRONTEND: none 27 | DEBIAN_FRONTEND: noninteractive 28 | steps: 29 | - name: Prepare container (apt) 30 | run: | 31 | apt-get update -qq 32 | apt-get install -yq \ 33 | clang-${{ matrix.clang }} \ 34 | clang-tidy-${{ matrix.clang }} \ 35 | cmake \ 36 | git \ 37 | libbz2-dev \ 38 | libexpat1-dev \ 39 | libgdal-dev \ 40 | libgeos-dev \ 41 | liblz4-dev \ 42 | make \ 43 | zlib1g-dev 44 | shell: bash 45 | - uses: actions/checkout@v4 46 | with: 47 | submodules: true 48 | - uses: ./.github/actions/install-from-git 49 | - uses: ./.github/actions/cmake 50 | - name: Run clang-tidy 51 | run: make clang-tidy | tee osmcoastline-${{ github.sha }}-clang-tidy-${{ matrix.clang }}.log 52 | shell: bash 53 | working-directory: build 54 | - name: Upload clang-tidy log 55 | uses: actions/upload-artifact@v4 56 | if: always() 57 | with: 58 | name: osmcoastline-${{ github.sha }}-clang-tidy-${{ matrix.clang }}-log 59 | path: build/osmcoastline-${{ github.sha }}-clang-tidy-${{ matrix.clang }}.log 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # Change Log 3 | 4 | All notable changes to this project will be documented in this file. 5 | This project adheres to [Semantic Versioning](https://semver.org/). 6 | 7 | ## [unreleased] - 8 | 9 | ### Added 10 | 11 | ### Changed 12 | 13 | ### Fixed 14 | 15 | 16 | ## [2.4.1] - 2025-01-14 17 | 18 | ### Added 19 | 20 | - Add option `-e, --exit-ignore-warnings`: Return with exit code 0 even if 21 | there are warnings. 22 | 23 | ### Changed 24 | 25 | - Modernized code and CMake config, updates for new versions of libraries 26 | used. 27 | 28 | 29 | ## [2.4.0] - 2022-12-22 30 | 31 | ### Added 32 | 33 | - `--format` option to `osmcoastline_filter` to set output file format. 34 | 35 | ### Changed 36 | 37 | - Now needs at least C++14 and CMake 3.10. 38 | - Various small code cleanups. 39 | 40 | ### Fixed 41 | 42 | - Do not create empty polygons in water output. 43 | 44 | 45 | ## [2.3.1] - 2021-09-02 46 | 47 | ### Added 48 | 49 | - GPKG output support 50 | 51 | ### Changed 52 | 53 | - More tests, specifically for EPSG:3857. 54 | - More error checks. 55 | - Some shell script cleanups. 56 | 57 | ### Fixed 58 | 59 | - Fix axis order problem with GDAL 3: In GDAL 3 the axis order for WGS84 60 | changed from (lon, lat) to (lat, lon)! So we need to use the magic "CRS84" 61 | instead which does the same thing in GDAL 2 and GDAL 3. This is an important 62 | fix, without it osmcoastline doesn't work with GDAL 3 when using any output 63 | SRS other than WGS84. 64 | 65 | ## [2.3.0] - 2021-01-08 66 | 67 | ### Added 68 | 69 | - Add `-g`, `--gdal-driver=DRIVER` option to `osmcoastline`. This allows 70 | writing results to a shapefile or other format, not only to sqlite files. 71 | 72 | ### Changed 73 | 74 | - Various small fixes and cleanups. 75 | - Now depends on libosmium 2.16.0 or greater. This allows compiling with 76 | support for PBF lz4 compression which is enabled by default if the 77 | library is found. Disable by setting CMake option `WITH_LZ4` to `OFF`. 78 | 79 | ### Fixed 80 | 81 | - Segfault in osmcoastline with newer GDAL versions (#39) 82 | 83 | 84 | ## [2.2.4] - 2019-02-27 85 | 86 | ### Changed 87 | 88 | - Also look for newer clang-tidy versions in CMake config. 89 | 90 | ### Fixed 91 | 92 | - Put Antarctic closure to exactly +/- 180 degree longitude. 93 | - Add try/catch around most of main so we don't end with exception. 94 | 95 | 96 | ## [2.2.3] - 2019-02-06 97 | 98 | ### Fixed 99 | 100 | - Compile with `NDEBUG` in `RelWithDebInfo` mode. 101 | - Better error reporting on some exceptions. 102 | 103 | 104 | ## [2.2.2] - 2019-02-03 105 | 106 | ### Fixed 107 | 108 | - Make `--output-lines` work even if `--output-polygons` is set to `none`. 109 | 110 | 111 | ## [2.2.1] - 2018-12-07 112 | 113 | ### Added 114 | 115 | - We have now proper test cases. Just a few, but at least there is a framework 116 | for automated testing now. 117 | 118 | ### Changed 119 | 120 | - Various small changes in the code and manuals to make it clearer. 121 | 122 | ### Fixed 123 | 124 | - Various small bugs were fixed that lead to crashes in unusual circumstances. 125 | 126 | 127 | ## [2.2.0] - 2018-09-05 128 | 129 | ### Added 130 | 131 | - Add spatialite scripts for creating grids for splitting. 132 | - CMake config adds `clang-tidy` target. 133 | 134 | ### Changed 135 | 136 | - Use `OGC_FID` instead of `ID` as id column in SQL scripts, that's how OGR 137 | expects it. 138 | - Update to newer Protozero and Libosmium. 139 | - Various small code-cleanup changes. 140 | - Output extended version information on `--verbose` and `--version`. 141 | - Derive exception used from `std::runtime_error`. 142 | - Update to newest gdalcpp. 143 | 144 | ### Fixed 145 | 146 | - Initialize stats with 0. 147 | - `osmcoastline_ways`: Delete the copy and move constructor/assignment because 148 | we have a special destructor. 149 | - Add `-pthread` compiler and linker options. 150 | - Fix undefined behavior that resulted in more or less coastlines reported 151 | as "questionable". 152 | - Lower right corner of Antarctica was being cut off in EPSG:3857. 153 | - Very narrow water polygons were output near the anti-meridian in Antarctica. 154 | 155 | 156 | ## [2.1.4] - 2016-09-16 157 | 158 | ### Changed 159 | 160 | - Miscellaneous code cleanups. 161 | 162 | ### Fixed 163 | 164 | - Windows build. 165 | 166 | 167 | ## [2.1.3] - 2016-03-30 168 | 169 | ### Added 170 | 171 | - Add verbose option to `osmcoastline_filter`. 172 | - `osmcoastline_filter` now shows memory used in verbose mode. 173 | 174 | ### Changed 175 | 176 | - Optimized `osmcoastline_filter` program. 177 | - Use more features from newest libosmium. 178 | 179 | ### Fixed 180 | 181 | - Setting the sqlite output to unsynchronized speeds up writing to database. 182 | - Now also works on GDAL 2. Fixes an error in the transaction handling. 183 | 184 | 185 | ## [2.1.2] - 2016-01-05 186 | 187 | ### Added 188 | 189 | - Add --help/-h and --version/-V options to all programs. 190 | 191 | ### Changed 192 | 193 | - Use a better approximation for the southernmost coordinate for Mercator 194 | projection. 195 | - Updated for newest libosmium version (2.5.2). 196 | - Uses gdalcpp.hpp from https://github.com/joto/gdalcpp instead of directly 197 | talking to GDAL/OGR. Makes this compatible with GDAL 2. 198 | - Improved internal code using `unique_ptr` where possible. 199 | 200 | ### Fixed 201 | 202 | - "Fixed" flag in rings layer now correct. 203 | 204 | 205 | ## [2.1.1] - 2015-08-31 206 | 207 | ### Changed 208 | 209 | - Use newest libosmium release. 210 | 211 | 212 | ## [2.1.0] - 2015-08-18 213 | 214 | ### Added 215 | 216 | - Optionally writes out list of all coastline segments and the new program 217 | `osmcoastline_segments` can compare those lists in various ways. 218 | 219 | ### Changed 220 | 221 | - Updates for new libosmium version 222 | 223 | 224 | ## [2.0.1] - 2015-03-31 225 | 226 | ### Changed 227 | 228 | - Added man pages 229 | 230 | 231 | [unreleased]: https://github.com/osmcode/osmium-tool/compare/v2.4.0...HEAD 232 | [2.4.0]: https://github.com/osmcode/osmium-tool/compare/v2.3.1...v2.4.0 233 | [2.3.1]: https://github.com/osmcode/osmium-tool/compare/v2.3.0...v2.3.1 234 | [2.3.0]: https://github.com/osmcode/osmium-tool/compare/v2.2.4...v2.3.0 235 | [2.2.4]: https://github.com/osmcode/osmium-tool/compare/v2.2.3...v2.2.4 236 | [2.2.3]: https://github.com/osmcode/osmium-tool/compare/v2.2.2...v2.2.3 237 | [2.2.2]: https://github.com/osmcode/osmium-tool/compare/v2.2.1...v2.2.2 238 | [2.2.1]: https://github.com/osmcode/osmium-tool/compare/v2.2.0...v2.2.1 239 | [2.2.0]: https://github.com/osmcode/osmium-tool/compare/v2.1.4...v2.2.0 240 | [2.1.4]: https://github.com/osmcode/osmium-tool/compare/v2.1.3...v2.1.4 241 | [2.1.3]: https://github.com/osmcode/osmium-tool/compare/v2.1.2...v2.1.3 242 | [2.1.2]: https://github.com/osmcode/osmium-tool/compare/v2.1.1...v2.1.2 243 | [2.1.1]: https://github.com/osmcode/osmium-tool/compare/v2.1.0...v2.1.1 244 | [2.1.0]: https://github.com/osmcode/osmium-tool/compare/v2.0.1...v2.1.0 245 | [2.0.1]: https://github.com/osmcode/osmium-tool/compare/v2.0.0...v2.0.1 246 | 247 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #----------------------------------------------------------------------------- 2 | # 3 | # CMake Config 4 | # 5 | # OSMCoastline 6 | # 7 | #----------------------------------------------------------------------------- 8 | 9 | cmake_minimum_required(VERSION 3.10 FATAL_ERROR) 10 | 11 | project(osmcoastline VERSION 2.4.1 LANGUAGES CXX C) 12 | 13 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") 14 | 15 | #----------------------------------------------------------------------------- 16 | 17 | set(AUTHOR "Jochen Topf ") 18 | 19 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 20 | 21 | option(WITH_LZ4 "Build with lz4 support for PBF files" ON) 22 | 23 | 24 | #----------------------------------------------------------------------------- 25 | # 26 | # Find external dependencies 27 | # 28 | #----------------------------------------------------------------------------- 29 | 30 | include_directories(include) 31 | 32 | find_package(Osmium 2.16.0 COMPONENTS io gdal) 33 | include_directories(SYSTEM ${OSMIUM_INCLUDE_DIRS}) 34 | 35 | if(WITH_LZ4) 36 | find_package(LZ4) 37 | 38 | if(LZ4_FOUND) 39 | message(STATUS "lz4 library found, compiling with it") 40 | add_definitions(-DOSMIUM_WITH_LZ4) 41 | include_directories(SYSTEM ${LZ4_INCLUDE_DIRS}) 42 | list(APPEND OSMIUM_IO_LIBRARIES ${LZ4_LIBRARIES}) 43 | else() 44 | message(WARNING "lz4 library not found, compiling without it") 45 | endif() 46 | else() 47 | message(STATUS "Building without lz4 support: Set WITH_LZ4=ON to change this") 48 | endif() 49 | 50 | if(MSVC) 51 | find_path(GETOPT_INCLUDE_DIR getopt.h) 52 | find_library(GETOPT_LIBRARY NAMES wingetopt) 53 | if(GETOPT_INCLUDE_DIR AND GETOPT_LIBRARY) 54 | include_directories(${GETOPT_INCLUDE_DIR}) 55 | else() 56 | set(GETOPT_MISSING 1) 57 | endif() 58 | endif() 59 | 60 | 61 | #----------------------------------------------------------------------------- 62 | # 63 | # Decide which C++ version to use (Minimum/default: C++14). 64 | # 65 | #----------------------------------------------------------------------------- 66 | if(NOT MSVC) 67 | if(NOT USE_CPP_VERSION) 68 | set(USE_CPP_VERSION c++14) 69 | endif() 70 | message(STATUS "Use C++ version: ${USE_CPP_VERSION}") 71 | # following only available from cmake 2.8.12: 72 | # add_compile_options(-std=${USE_CPP_VERSION}) 73 | # so using this instead: 74 | add_definitions(-std=${USE_CPP_VERSION}) 75 | endif() 76 | 77 | 78 | #----------------------------------------------------------------------------- 79 | # 80 | # Compiler and Linker flags 81 | # 82 | #----------------------------------------------------------------------------- 83 | if(MSVC) 84 | set(DEV_COMPILE_OPTIONS "/Ox") 85 | set(RWD_COMPILE_OPTIONS "/Ox /DNDEBUG") 86 | else() 87 | set(DEV_COMPILE_OPTIONS "-O3 -g") 88 | set(RWD_COMPILE_OPTIONS "-O3 -g -DNDEBUG") 89 | endif() 90 | 91 | if(WIN32) 92 | add_definitions(-DWIN32 -D_WIN32 -DMSWIN32 -DBGDWIN32 93 | -DWINVER=0x0500 -D_WIN32_WINNT=0x0500 -D_WIN32_IE=0x0600) 94 | endif() 95 | 96 | set(CMAKE_CXX_FLAGS_DEV "${DEV_COMPILE_OPTIONS}" 97 | CACHE STRING "Flags used by the compiler during developer builds." 98 | FORCE) 99 | 100 | set(CMAKE_EXE_LINKER_FLAGS_DEV "" 101 | CACHE STRING "Flags used by the linker during developer builds." 102 | FORCE) 103 | mark_as_advanced( 104 | CMAKE_CXX_FLAGS_DEV 105 | CMAKE_EXE_LINKER_FLAGS_DEV 106 | ) 107 | 108 | set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${RWD_COMPILE_OPTIONS}" 109 | CACHE STRING "Flags used by the compiler during RELWITHDEBINFO builds." 110 | FORCE) 111 | 112 | 113 | #----------------------------------------------------------------------------- 114 | # 115 | # Build Type 116 | # 117 | #----------------------------------------------------------------------------- 118 | set(CMAKE_CONFIGURATION_TYPES "Debug Release RelWithDebInfo MinSizeRel Dev") 119 | 120 | # In 'Dev' mode: compile with very strict warnings and turn them into errors. 121 | if(CMAKE_BUILD_TYPE STREQUAL "Dev") 122 | if(NOT MSVC) 123 | add_definitions(-Werror -fno-omit-frame-pointer) 124 | endif() 125 | add_definitions(${OSMIUM_WARNING_OPTIONS}) 126 | endif() 127 | 128 | # Force RelWithDebInfo build type if none was given 129 | if(CMAKE_BUILD_TYPE) 130 | set(build_type ${CMAKE_BUILD_TYPE}) 131 | else() 132 | set(build_type "RelWithDebInfo") 133 | endif() 134 | 135 | set(CMAKE_BUILD_TYPE ${build_type} 136 | CACHE STRING 137 | "Choose the type of build, options are: ${CMAKE_CONFIGURATION_TYPES}." 138 | FORCE) 139 | 140 | 141 | #----------------------------------------------------------------------------- 142 | # 143 | # Optional "clang-tidy" target 144 | # 145 | #----------------------------------------------------------------------------- 146 | message(STATUS "Looking for clang-tidy") 147 | find_program(CLANG_TIDY NAMES clang-tidy clang-tidy-20 clang-tidy-19 clang-tidy-18 clang-tidy-17 clang-tidy-16 clang-tidy-15 clang-tidy-14) 148 | 149 | if(CLANG_TIDY) 150 | message(STATUS "Looking for clang-tidy - found ${CLANG_TIDY}") 151 | 152 | file(GLOB _all_code src/*.cpp) 153 | 154 | add_custom_target(clang-tidy 155 | ${CLANG_TIDY} 156 | -p ${CMAKE_BINARY_DIR} 157 | ${_all_code} 158 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/src" 159 | ) 160 | else() 161 | message(STATUS "Looking for clang-tidy - not found") 162 | message(STATUS " Build target 'clang-tidy' will not be available.") 163 | endif() 164 | 165 | #----------------------------------------------------------------------------- 166 | # 167 | # Optional "cppcheck" target that checks C++ code 168 | # 169 | #----------------------------------------------------------------------------- 170 | message(STATUS "Looking for cppcheck") 171 | find_program(CPPCHECK cppcheck) 172 | 173 | if(CPPCHECK) 174 | message(STATUS "Looking for cppcheck - found") 175 | set(CPPCHECK_OPTIONS --enable=all) 176 | 177 | # cpp doesn't find system includes for some reason, suppress that report 178 | set(CPPCHECK_OPTIONS ${CPPCHECK_OPTIONS} --suppress=missingIncludeSystem) 179 | 180 | file(GLOB ALL_CODE src/*.cpp) 181 | 182 | set(CPPCHECK_FILES ${ALL_CODE}) 183 | 184 | add_custom_target(cppcheck 185 | ${CPPCHECK} 186 | --std=c++14 ${CPPCHECK_OPTIONS} 187 | ${CPPCHECK_FILES} 188 | ) 189 | else() 190 | message(STATUS "Looking for cppcheck - not found") 191 | message(STATUS " Build target 'cppcheck' will not be available.") 192 | endif(CPPCHECK) 193 | 194 | 195 | #----------------------------------------------------------------------------- 196 | # 197 | # Optional "iwyu" target to check headers 198 | # https://include-what-you-use.org/ 199 | # 200 | #----------------------------------------------------------------------------- 201 | find_program(IWYU_TOOL NAMES iwyu_tool iwyu_tool.py) 202 | 203 | if(IWYU_TOOL) 204 | message(STATUS "Looking for iwyu_tool.py - found") 205 | add_custom_target(iwyu ${IWYU_TOOL} -p ${CMAKE_BINARY_DIR}) 206 | else() 207 | message(STATUS "Looking for iwyu_tool.py - not found") 208 | message(STATUS " Make target iwyu not available") 209 | endif() 210 | 211 | 212 | #----------------------------------------------------------------------------- 213 | # 214 | # Optional "man" target to generate man pages 215 | # 216 | #----------------------------------------------------------------------------- 217 | message(STATUS "Looking for pandoc") 218 | find_program(PANDOC pandoc) 219 | 220 | function(add_man_page _section _name) 221 | file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/man/man${_section}) 222 | set(_output_file ${CMAKE_BINARY_DIR}/man/man${_section}/${_name}.${_section}) 223 | set(_source_file ${CMAKE_SOURCE_DIR}/man/${_name}.md) 224 | set(_install_dir "share/man/man{$_section}") 225 | string(TOUPPER ${_name} _name_upcase) 226 | add_custom_command(OUTPUT ${_output_file} 227 | COMMAND ${PANDOC} 228 | ${PANDOC_MAN_OPTIONS} 229 | --variable "title=${_name_upcase}" 230 | --variable "section=${_section}" 231 | -o ${_output_file} 232 | ${_source_file} 233 | DEPENDS ${_source_file} man/manpage.template 234 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 235 | COMMENT "Building manpage ${_name}.${_section}" 236 | VERBATIM) 237 | set(ALL_MAN_PAGES "${ALL_MAN_PAGES};${_output_file}" PARENT_SCOPE) 238 | endfunction() 239 | 240 | 241 | if(PANDOC) 242 | message(STATUS "Looking for pandoc - found") 243 | message(STATUS " Manual pages will be built") 244 | set(PANDOC_MAN_OPTIONS 245 | -s 246 | -t man 247 | --template ${CMAKE_CURRENT_SOURCE_DIR}/man/manpage.template 248 | --variable "description=osmcoastline/${PROJECT_VERSION}" 249 | --variable "version=${PROJECT_VERSION}" 250 | --variable "author=${AUTHOR}" 251 | ) 252 | set(PANDOC_HTML_OPTIONS -s -t html) 253 | 254 | add_man_page(1 osmcoastline) 255 | add_man_page(1 osmcoastline_filter) 256 | add_man_page(1 osmcoastline_readmeta) 257 | add_man_page(1 osmcoastline_segments) 258 | add_man_page(1 osmcoastline_ways) 259 | 260 | install(DIRECTORY ${CMAKE_BINARY_DIR}/man DESTINATION share) 261 | 262 | add_custom_target(man ALL DEPENDS ${ALL_MAN_PAGES}) 263 | else() 264 | message(STATUS "Looking for pandoc - not found") 265 | message(STATUS " Manual pages will not be built") 266 | endif(PANDOC) 267 | 268 | 269 | #----------------------------------------------------------------------------- 270 | # 271 | # Version 272 | # 273 | #----------------------------------------------------------------------------- 274 | 275 | find_package(Git) 276 | 277 | if(GIT_FOUND) 278 | execute_process(COMMAND "${GIT_EXECUTABLE}" describe --tags --dirty=-changed 279 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 280 | OUTPUT_VARIABLE VERSION_FROM_GIT 281 | ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) 282 | if(VERSION_FROM_GIT) 283 | set(VERSION_FROM_GIT " (${VERSION_FROM_GIT})") 284 | endif() 285 | endif() 286 | 287 | configure_file( 288 | ${PROJECT_SOURCE_DIR}/src/version.cpp.in 289 | ${PROJECT_BINARY_DIR}/src/version.cpp 290 | ) 291 | 292 | 293 | #----------------------------------------------------------------------------- 294 | 295 | find_library(GEOS_C_LIBRARIES NAMES geos_c) 296 | include_directories (SYSTEM ${GEOS_C_INCLUDE_DIR}) 297 | 298 | add_definitions(${OSMIUM_WARNING_OPTIONS}) 299 | 300 | add_subdirectory(src) 301 | 302 | configure_file( 303 | ${PROJECT_SOURCE_DIR}/runtest.sh.in 304 | ${PROJECT_BINARY_DIR}/runtest.sh 305 | ) 306 | 307 | configure_file( 308 | ${PROJECT_SOURCE_DIR}/coastline_sqlite.qgs 309 | ${PROJECT_BINARY_DIR}/coastline_sqlite.qgs 310 | ) 311 | 312 | configure_file( 313 | ${PROJECT_SOURCE_DIR}/coastline_sources.qgs 314 | ${PROJECT_BINARY_DIR}/coastline_sources.qgs 315 | ) 316 | 317 | configure_file( 318 | ${PROJECT_SOURCE_DIR}/osmcoastline_readmeta 319 | ${PROJECT_BINARY_DIR}/osmcoastline_readmeta 320 | @ONLY 321 | ) 322 | install(PROGRAMS osmcoastline_readmeta DESTINATION bin) 323 | 324 | 325 | #----------------------------------------------------------------------------- 326 | # 327 | # Documentation 328 | # 329 | #----------------------------------------------------------------------------- 330 | 331 | add_subdirectory(doc) 332 | 333 | 334 | #----------------------------------------------------------------------------- 335 | # 336 | # Tests 337 | # 338 | #----------------------------------------------------------------------------- 339 | 340 | enable_testing() 341 | add_subdirectory(test) 342 | 343 | 344 | #----------------------------------------------------------------------------- 345 | # 346 | # Packaging 347 | # 348 | #----------------------------------------------------------------------------- 349 | 350 | if(WIN32) 351 | set(CPACK_GENERATOR ZIP) 352 | else() 353 | set(CPACK_GENERATOR TGZ) 354 | endif() 355 | 356 | include(CPack) 357 | 358 | 359 | #----------------------------------------------------------------------------- 360 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # OSMCoastline 3 | 4 | OSMCoastline extracts the coastline data from an OSM planet file and assembles 5 | all the pieces into polygons for use in map renderers etc. 6 | 7 | https://osmcode.org/osmcoastline 8 | 9 | [![Build Status](https://github.com/osmcode/osmcoastline/actions/workflows/ci.yml/badge.svg)](https://github.com/osmcode/osmcoastline/actions) 10 | 11 | ## Prerequisites 12 | 13 | ### Libosmium 14 | 15 | https://github.com/osmcode/libosmium 16 | https://osmcode.org/libosmium 17 | At least version 2.16.0 is needed. 18 | 19 | ### Protozero 20 | 21 | https://github.com/mapbox/protozero 22 | Debian/Ubuntu: libprotozero-dev 23 | At least version 1.6.1 is needed. 24 | 25 | ### LZ4 (optional) 26 | 27 | https://lz4.github.io/lz4/ 28 | Debian/Ubuntu: liblz4-dev 29 | 30 | Only needed for LZ4 PBF compression. 31 | 32 | ### zlib (for PBF support) 33 | 34 | https://www.zlib.net/ 35 | Debian/Ubuntu: zlib1g-dev 36 | 37 | ### GDAL (for OGR support) 38 | 39 | https://gdal.org/ 40 | Debian/Ubuntu: libgdal1-dev 41 | (Must be built with Spatialite and GEOS support which is true for 42 | Debian/Ubuntu packages. You need GDAL 2 or greater. 43 | 44 | ### GEOS 45 | 46 | https://trac.osgeo.org/geos/ 47 | Debian/Ubuntu: libgeos-dev 48 | 49 | ### Sqlite/Spatialite 50 | 51 | https://www.gaia-gis.it/fossil/libspatialite/index 52 | Debian/Ubuntu: sqlite3, spatialite-bin 53 | 54 | ### Pandoc (optional, to build documentation) 55 | 56 | https://pandoc.org/ 57 | Debian/Ubuntu: pandoc 58 | (If pandoc is found by CMake, the manpages will automatically be built.) 59 | 60 | 61 | ## Building 62 | 63 | You'll need the prerequisites including `libosmium` installed. 64 | 65 | OSMCoastline uses CMake for building: 66 | 67 | mkdir build 68 | cd build 69 | cmake .. 70 | make 71 | 72 | Call `make doc` to build the Doxygen API documentation which will be available 73 | in the `doc/html` directory. 74 | 75 | 76 | ## Testing 77 | 78 | A few tests are provided that can be run by calling `ctest`. 79 | 80 | The tests themselves are written as shell scripts and can be found in the 81 | `test/t` directory. Some test use the `nodegrid2opl` helper program found in 82 | the `src` directory, it has some documentation in the source code. 83 | 84 | 85 | ## The `runtest` script 86 | 87 | Run the script `runtest.sh` from the directory you built the program in. It 88 | will read the supplied `testdata.osm` and create output in the `testdata.db` 89 | spatialite database. 90 | 91 | It is normal for this program to create errors and warnings, because it is 92 | testing a rather broken input file. You will get messages such as "Closing 93 | ring between node -84 and node -74" and "Warning 1: Self-intersection 94 | at or near point 7.48488 53.8169". At the end it should print: 95 | 96 | There were 35 warnings. 97 | There were 1 errors. 98 | 99 | You can use the supplied `coastline_sqlite.qgs` QGIS project file to open the 100 | output with QGIS. 101 | 102 | Call `runtest.sh -v` to run the tests under Valgrind. 103 | 104 | 105 | ## Running 106 | 107 | Note that you might want to run `osmcoastline_filter` first, see below under 108 | **Filtering**. 109 | 110 | Run: `osmcoastline -o DBFILE PLANET-FILE` 111 | 112 | For example: `osmcoastline -o coastline.db planet.osm.pbf` 113 | 114 | This will create a spatialite database named `DBFILE` and write several tables 115 | with the output into it. 116 | 117 | Use `--verbose` to see whats going on. Start with `--help` to see other 118 | options. 119 | 120 | 121 | ## Output 122 | 123 | By default, the output is a spatialite database with the following tables. All 124 | tables are always created but depending on the command line options only some of 125 | them might contain anything. 126 | 127 | * `error_lines` Lines that have errors (for instance not closed rings or 128 | self-intersections). 129 | 130 | * `error_points` Problematic points such as intersections. 131 | 132 | * `rings` Coastline rings as linestrings. The table is not populated by default, 133 | because this is only needed for finding errors in the coastline. Use the 134 | command line option `--output-rings` to populate this table. 135 | 136 | * `land_polygons` Finished assembled land polygons. Depending on `--max-points` 137 | option this will contain complete or split polygons. Only filled if the 138 | option `--output-polygons=land` (thats the default) or `=both` has been given. 139 | 140 | * `water_polygons` Finished assembled water polygons. Only filled if option 141 | `--output-polygons=water` or `=both` has been given. 142 | 143 | * `lines` Coastlines as linestrings. Depending on `--max-points` option this 144 | will contain complete or split linestrings. Only filled if the option 145 | `--output-lines` has been given. 146 | 147 | By default all output is in WGS84. You can use the option `--srs=3857` to 148 | create output in "Web Mercator". (Other projections are currently not 149 | supported.) 150 | 151 | 152 | 153 | By default geometry indexes are created for all tables. This makes the database 154 | larger, but faster to use. You can use the option `--no-index` to suppress 155 | this, for instance if you never use the data directly anyway but want to 156 | transform it into something else. 157 | 158 | Coastlines and polygons are never simplified, but contain the full detail. See 159 | the `simplify_and_split_spatialite` or the `simplify_and_split_postgis` 160 | directories for scripts that help with simplifying and splitting geometries 161 | using Spatialite or PostGIS, respectively. 162 | 163 | The database tables `options` and `meta` contain the command line options 164 | used to create the database and some metadata. You can use the script 165 | `osmcoastline_readmeta` to look at them. 166 | 167 | By default, OSMCoastline creates a spatialite database. If you need shapefiles 168 | use ogr2ogr to convert the data: 169 | 170 | ogr2ogr -f "ESRI Shapefile" land_polygons.shp coastline.db land_polygons 171 | 172 | Alternatively, OSMCoastline aims to support all geospatial data formats as the 173 | output (e.g. Shapefile, by setting GDAL driver as "ESRI Shapefile"). If a data 174 | format other than Spatialite database is selected as the output format, the two 175 | database tables `options` and `meta` will be omitted and geometry indexes will 176 | not be created. 177 | 178 | 179 | ## Steps 180 | 181 | OSMCoastline runs in several steps, each can optionally create some output. 182 | In most cases you will only be interested in the end result but preliminary 183 | results are supplied for debugging or other special uses. 184 | 185 | **Step 1**: Filter out all nodes and ways tagged `natural=coastline` and all 186 | nodes needed by those ways. (This can also be done with the 187 | `osmcoastline_filter` program, see below) 188 | 189 | **Step 2**: Assemble all coastline ways into rings. Rings that are not closed 190 | in the OSM data will be closed depending on the `--close-distance` 191 | option. 192 | 193 | **Step 3**: Assemble polygons from the rings, possibly including holes for 194 | water areas. 195 | 196 | **Step 4**: Split up large polygons into smaller ones. The options 197 | `--max-points` and `--bbox-overlap` are used here. 198 | 199 | **Step 5**: Create water polygons as the "inverse" of the land polygons. 200 | 201 | The errors encountered in each step are written to the `error_points` and 202 | `error_lines` tables. 203 | 204 | 205 | ## Options 206 | 207 | -c, --close-distance=DISTANCE 208 | 209 | OSMCoastline assembles ways tagged `natural=coastline` into rings. Sometimes 210 | there is a gap in the coastline in the OSM data. OSMCoastline will close this 211 | gap if it is smaller than DISTANCE. Use 0 to disable this feature. 212 | 213 | -b, --bbox-overlap=OVERLAP 214 | 215 | Polygons that are too large are split into two halves (recursively if need be). 216 | Where the polygons touch the OVERLAP is added, because two polygons just 217 | touching often lead to rendering problems. The value is given in the units used 218 | for the projection (for WGS84 (4326) this is in degrees, for Mercator (3857) 219 | this is in meters). If this is set too small you might get rendering artefacts 220 | where polygons touch. The larger you set this the larger the output polygons 221 | will be. The best values depend on the map scale or zoom level you are 222 | preparing the data for. Disable the overlap by setting it to 0. Default is 223 | 0.0001 for WGS84 and 10 for Mercator. 224 | 225 | -m, --max-points=NUM 226 | 227 | Set this to 0 to prevent splitting of large polygons and linestrings. If set to 228 | any other positive integer OSMCoastline will try to split polygons/linestrings 229 | to not have more than this many points. Depending on the overlap defined with 230 | -b and the shape of the polygons it is sometimes not possible to get the 231 | polygons small enough. OSMCoastline will warn you on stderr if this is the 232 | case. Default is 1000. 233 | 234 | -s, --srs=EPSGCODE 235 | 236 | Set spatial reference system/projection. Use 4326 for WGS84 or 3857 for "Web 237 | Mercator". If you want to use the data for the usual tiled web maps, 3857 is 238 | probably right. For other uses, especially if you want to re-project to some 239 | other projection, 4326 is probably right. Other projections are currently not 240 | supported. Default is 4326. 241 | 242 | -v, --verbose 243 | 244 | Gives you detailed information on what osmcoastline is doing, including timing. 245 | 246 | Run `osmcoastline --help` to see all options. 247 | 248 | 249 | ## Return codes 250 | 251 | `osmcoastline` uses the following return codes: 252 | 253 | 0 - OK 254 | 1 - Warning 255 | 2 - Error 256 | 3 - Fatal error (output file could not be opened etc.) 257 | 4 - Error parsing command line arguments 258 | 259 | The difference between warnings and errors is somewhat muddy. Warnings are 260 | geometry problems that have either been fixed automatically or seem to be 261 | small. Errors are larger problems that couldn't be fixed. If there were errors 262 | you probably do not want to use the generated data but fix the OSM data first. 263 | If there were warnings the data might be okay, but there still could be data 264 | missing or geometry problems such as self-intersections in the coastline. But 265 | the classification of problems into warnings and errors is difficult, so to be 266 | on the safe side you might only want to use the data if there are no warnings 267 | and no errors at all. 268 | 269 | 270 | ## Antarctica 271 | 272 | OSMCoastline has special code for the coastline of Antarctica. This is the only 273 | coastline that can remain open. The coastline starts somewhere around 180° 274 | East, 77° South and ends around 180° West and 77° South. OSMCoastline will find 275 | those open ends and connect them by adding several "nodes" forming a proper 276 | polygon. Depending on the output projection (EPSG:4326 or EPSG:3857) this 277 | polygon will either extend to the South Pole or to the 85.0511° line. 278 | 279 | 280 | ## Filtering 281 | 282 | The program `osmcoastline_filter` can be used to filter from an OSM planet file 283 | all nodes and ways needed for building the coastlines and write them out in 284 | OSM format. The resulting file will be a lot smaller (less than 1%) than the 285 | original planet file, but it contains everything needed to assemble the 286 | coastline polygons. 287 | 288 | If you are playing around or want to run `osmcoastline` several times with 289 | different parameters, run `osmcoastline_filter` once first and use its output 290 | as the input for `osmcoastline`. 291 | 292 | Run it as follows: `osmcoastline_filter -o OUTFILE.osm.pbf INFILE.osm.pbf` 293 | 294 | `osmcoastline_filter` can read PBF and XML files, but write only PBF files. PBF 295 | files are much smaller and faster to read and write. 296 | 297 | 298 | ## Extracts 299 | 300 | Generally you can not run OSMCoastline on extracts. OSMCoastline assembles ways 301 | into continuous coastline linestrings and then into land and water polygons. 302 | But if the coastline is not closed, it can't do that. This might work if your 303 | extract only contains an island, but in most cases this will not work. 304 | 305 | 306 | ## License 307 | 308 | OSMCoastline is available under the GNU GPL version 3 or later. 309 | 310 | 311 | ## Authors 312 | 313 | Jochen Topf (jochen@topf.org) 314 | 315 | -------------------------------------------------------------------------------- /cmake/FindLZ4.cmake: -------------------------------------------------------------------------------- 1 | find_path(LZ4_INCLUDE_DIR 2 | NAMES lz4.h 3 | DOC "lz4 include directory") 4 | mark_as_advanced(LZ4_INCLUDE_DIR) 5 | find_library(LZ4_LIBRARY 6 | NAMES lz4 liblz4 7 | DOC "lz4 library") 8 | mark_as_advanced(LZ4_LIBRARY) 9 | 10 | if (LZ4_INCLUDE_DIR) 11 | file(STRINGS "${LZ4_INCLUDE_DIR}/lz4.h" _lz4_version_lines 12 | REGEX "#define[ \t]+LZ4_VERSION_(MAJOR|MINOR|RELEASE)") 13 | string(REGEX REPLACE ".*LZ4_VERSION_MAJOR *\([0-9]*\).*" "\\1" _lz4_version_major "${_lz4_version_lines}") 14 | string(REGEX REPLACE ".*LZ4_VERSION_MINOR *\([0-9]*\).*" "\\1" _lz4_version_minor "${_lz4_version_lines}") 15 | string(REGEX REPLACE ".*LZ4_VERSION_RELEASE *\([0-9]*\).*" "\\1" _lz4_version_release "${_lz4_version_lines}") 16 | set(LZ4_VERSION "${_lz4_version_major}.${_lz4_version_minor}.${_lz4_version_release}") 17 | unset(_lz4_version_major) 18 | unset(_lz4_version_minor) 19 | unset(_lz4_version_release) 20 | unset(_lz4_version_lines) 21 | endif () 22 | 23 | include(FindPackageHandleStandardArgs) 24 | find_package_handle_standard_args(LZ4 25 | REQUIRED_VARS LZ4_LIBRARY LZ4_INCLUDE_DIR 26 | VERSION_VAR LZ4_VERSION) 27 | 28 | if (LZ4_FOUND) 29 | set(LZ4_INCLUDE_DIRS "${LZ4_INCLUDE_DIR}") 30 | set(LZ4_LIBRARIES "${LZ4_LIBRARY}") 31 | 32 | if (NOT TARGET LZ4::LZ4) 33 | add_library(LZ4::LZ4 UNKNOWN IMPORTED) 34 | set_target_properties(LZ4::LZ4 PROPERTIES 35 | IMPORTED_LOCATION "${LZ4_LIBRARY}" 36 | INTERFACE_INCLUDE_DIRECTORIES "${LZ4_INCLUDE_DIR}") 37 | endif () 38 | endif () 39 | -------------------------------------------------------------------------------- /cmake/FindProtozero.cmake: -------------------------------------------------------------------------------- 1 | #---------------------------------------------------------------------- 2 | # 3 | # FindProtozero.cmake 4 | # 5 | # Find the protozero headers. 6 | # 7 | #---------------------------------------------------------------------- 8 | # 9 | # Usage: 10 | # 11 | # Copy this file somewhere into your project directory, where cmake can 12 | # find it. Usually this will be a directory called "cmake" which you can 13 | # add to the CMake module search path with the following line in your 14 | # CMakeLists.txt: 15 | # 16 | # list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") 17 | # 18 | # Then add the following in your CMakeLists.txt: 19 | # 20 | # find_package(Protozero [version] [REQUIRED]) 21 | # include_directories(SYSTEM ${PROTOZERO_INCLUDE_DIR}) 22 | # 23 | # The version number is optional. If it is not set, any version of 24 | # protozero will do. 25 | # 26 | # if(NOT PROTOZERO_FOUND) 27 | # message(WARNING "Protozero not found!\n") 28 | # endif() 29 | # 30 | #---------------------------------------------------------------------- 31 | # 32 | # Variables: 33 | # 34 | # PROTOZERO_FOUND - True if Protozero was found. 35 | # PROTOZERO_INCLUDE_DIR - Where to find include files. 36 | # 37 | #---------------------------------------------------------------------- 38 | 39 | # find include path 40 | find_path(PROTOZERO_INCLUDE_DIR protozero/version.hpp 41 | PATH_SUFFIXES include 42 | PATHS ${CMAKE_SOURCE_DIR}/../protozero 43 | ) 44 | 45 | # Check version number 46 | if(Protozero_FIND_VERSION) 47 | file(STRINGS "${PROTOZERO_INCLUDE_DIR}/protozero/version.hpp" _version_define REGEX "#define PROTOZERO_VERSION_STRING") 48 | if("${_version_define}" MATCHES "#define PROTOZERO_VERSION_STRING \"([0-9.]+)\"") 49 | set(_version "${CMAKE_MATCH_1}") 50 | else() 51 | set(_version "unknown") 52 | endif() 53 | endif() 54 | 55 | #set(PROTOZERO_INCLUDE_DIRS "${PROTOZERO_INCLUDE_DIR}") 56 | 57 | include(FindPackageHandleStandardArgs) 58 | find_package_handle_standard_args(Protozero 59 | REQUIRED_VARS PROTOZERO_INCLUDE_DIR 60 | VERSION_VAR _version) 61 | 62 | 63 | #---------------------------------------------------------------------- 64 | -------------------------------------------------------------------------------- /coastline.map: -------------------------------------------------------------------------------- 1 | # 2 | # Mapserver configuration 3 | # 4 | # For rendering using this file see render_image.sh. 5 | # 6 | MAP 7 | NAME coastline 8 | IMAGECOLOR 255 255 255 9 | 10 | LAYER 11 | NAME polygons 12 | TYPE POLYGON 13 | STATUS ON 14 | CONNECTIONTYPE OGR 15 | CONNECTION "coastline-mapserver.db" 16 | DATA "land_polygons" 17 | CLASS 18 | STYLE 19 | COLOR 0 0 0 20 | END 21 | END 22 | END 23 | 24 | END 25 | -------------------------------------------------------------------------------- /doc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #----------------------------------------------------------------------------- 2 | # 3 | # CMake Config 4 | # 5 | # OSMCoastline documentation 6 | # 7 | #----------------------------------------------------------------------------- 8 | 9 | message(STATUS "Configuring documentation") 10 | 11 | message(STATUS "Looking for doxygen") 12 | find_package(Doxygen) 13 | 14 | if(DOXYGEN_FOUND) 15 | message(STATUS "Looking for doxygen - found") 16 | configure_file(header.html ${CMAKE_CURRENT_BINARY_DIR}/header.html @ONLY) 17 | configure_file(Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY) 18 | add_custom_target(doc 19 | ${DOXYGEN_EXECUTABLE} 20 | ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile 21 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 22 | COMMENT "Generating API documentation with Doxygen" VERBATIM 23 | ) 24 | # install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/html" 25 | # DESTINATION "share/doc/libosmium-dev") 26 | else() 27 | message(STATUS "Looking for doxygen - not found") 28 | message(STATUS " Disabled making of documentation.") 29 | endif() 30 | 31 | #----------------------------------------------------------------------------- 32 | message(STATUS "Configuring documentation - done") 33 | 34 | 35 | #----------------------------------------------------------------------------- 36 | -------------------------------------------------------------------------------- /doc/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | $projectname: $title 10 | $title 11 | 12 | 13 | 14 | $treeview 15 | $search 16 | $mathjax 17 | 18 | $extrastylesheet 19 | 20 | 21 |
22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 38 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
33 |
$projectname 34 |  $projectnumber 35 |
36 |
$projectbrief
37 |
42 |
$projectbrief
43 |
$searchbox
54 |
55 | 56 | 57 | -------------------------------------------------------------------------------- /man/manpage.template: -------------------------------------------------------------------------------- 1 | $if(has-tables)$ 2 | .\"t 3 | $endif$ 4 | .TH "$title$" "$section$" "$version$" "$footer$" "$header$" 5 | $for(header-includes)$ 6 | $header-includes$ 7 | $endfor$ 8 | $for(include-before)$ 9 | $include-before$ 10 | $endfor$ 11 | $body$ 12 | $for(include-after)$ 13 | $include-after$ 14 | $endfor$ 15 | $if(author)$ 16 | .SH COPYRIGHT 17 | .PP 18 | Copyright (C) 2012\-2025 Jochen Topf . 19 | License GPLv3+: GNU GPL version 3 or later 20 | . 21 | This is free software: you are free to change and redistribute it. 22 | There is NO WARRANTY, to the extent permitted by law. 23 | .SH CONTACT 24 | .PP 25 | If you have any questions or want to report a bug, please go to 26 | https://osmcode.org/contact.html 27 | .SH AUTHORS 28 | $for(author)$$author$$sep$; $endfor$. 29 | $endif$ 30 | -------------------------------------------------------------------------------- /man/osmcoastline.md: -------------------------------------------------------------------------------- 1 | 2 | # NAME 3 | 4 | osmcoastline - extract coastline from OpenStreetMap data 5 | 6 | 7 | # SYNOPSIS 8 | 9 | **osmcoastline** \[*OPTIONS*\] \--output-database=*OUTPUT-DB* *INPUT-FILE* 10 | 11 | 12 | # DESCRIPTION 13 | 14 | **osmcoastline** extracts the coastline data from the *INPUT-FILE*, usually 15 | a planet file (or the output of the **osmcoastline_filter** program, see below) 16 | and assembles all the pieces into polygons for use in map renderers etc. 17 | 18 | The output is written to the Spatialite database *OUTPUT-DB*. Depending on the 19 | options it will contain the coastlines in different formats. See the 20 | description of the options below and the README.md for details. 21 | 22 | 23 | # OPTIONS 24 | 25 | -h, \--help 26 | : Display usage information. 27 | 28 | -b, \--bbox-overlap=OVERLAP 29 | : Polygons that are too large are split into two halves (recursively if need 30 | be). Where the polygons touch the OVERLAP is added, because two polygons 31 | just touching often lead to rendering problems. The value is given in the 32 | units used for the projection (for WGS84 (EPSG: 4326) this is in degrees, 33 | for Mercator (EPSG: 3857) this is in meters). If this is set too small you 34 | might get rendering artefacts where polygons touch. The larger you set 35 | this the larger the output polygons will be. The best values depend on 36 | the map scale or zoom level you are preparing the data for. Disable the 37 | overlap by setting it to 0. Default is 0.0001 for WGS84 and 10 for 38 | Mercator. 39 | 40 | -c, \--close-distance=DISTANCE 41 | : **osmcoastline** assembles ways tagged `natural=coastline` into rings. 42 | Sometimes there is a gap in the coastline in the OSM data. **osmcoastline** 43 | will close this gap if it is smaller than DISTANCE. Use 0 to disable this 44 | feature. 45 | 46 | -d, \--debug 47 | : Enable debugging output. 48 | 49 | -e, \--exit-ignore-warnings 50 | : Return with exit code 0 even if there are warnings. 51 | 52 | -f, \--overwrite 53 | : Overwrite output file if it already exists. 54 | 55 | -g, \--gdal-driver=DRIVER 56 | : Allows user to select any GDAL driver. Only "SQLite", "GPKG" and 57 | "ESRI Shapefile" GDAL drivers have been tested. The default is "SQLite". 58 | 59 | -i, \--no-index 60 | : Do not create spatial indexes in output db. The default is to create those 61 | indexes. This makes the database larger, but data access is faster. 62 | 63 | -l, \--output-lines 64 | : Output coastlines as lines to database file. 65 | 66 | -m, \--max-points=NUM 67 | : Set this to 0 to prevent splitting of large polygons and linestrings. If 68 | set to any other positive integer **osmcoastline** will try to split 69 | polygons/linestrings to not have more than this many points. Depending on 70 | the overlap defined with **-b** and the shape of the polygons it is 71 | sometimes not possible to get the polygons small enough. **osmcoastline** 72 | will warn you on STDERR if this is the case. Default is 1000. 73 | 74 | -o, \--output-database=FILE 75 | : Spatialite database file for output. This option must be set. 76 | 77 | -p, \--output-polygons=land|water|both|none 78 | : Which polygons to write out (default: land). 79 | 80 | -r, \--output-rings 81 | : Output rings to database file. This is used for debugging. 82 | 83 | -s, \--srs=EPSGCODE 84 | : Set spatial reference system/projection. Use 4326 for WGS84 or 3857 for 85 | "Web Mercator". If you want to use the data for the usual tiled web 86 | maps, 3857 is probably right. For other uses, especially if you want to 87 | re-project to some other projection, 4326 is probably right. Other 88 | projections are currently not supported. Default is 4326. 89 | 90 | -S, \--write-segments=FILENAME 91 | : Write out all coastline segments to the specified file. Segments are 92 | connections between two points. The segments are written in an internal 93 | format intended for use with the **osmcoastline_segments** program 94 | only. The file includes all segments actually in the OSM data and only 95 | those. Gaps are (possibly) closed in a later stage of running 96 | **osmcoastline**, but those closing segments will not be included. 97 | 98 | -v, \--verbose 99 | : Gives you detailed information on what **osmcoastline** is doing, 100 | including timing. 101 | 102 | -V, \--version 103 | : Display program version and license information. 104 | 105 | 106 | # NOTES 107 | 108 | To speed up processing you might want to run the **osmcoastline_filter** 109 | program first. See its man page for details. 110 | 111 | 112 | # DIAGNOSTICS 113 | 114 | **osmcoastline** exits with exit code 115 | 116 | 0 117 | ~ if everything was okay 118 | 119 | 1 120 | ~ if there were warnings while processing the coastline (unless -e is set) 121 | 122 | 2 123 | ~ if there were errors while processing the coastline 124 | 125 | 3 126 | ~ if there was a fatal error when running the program 127 | 128 | 4 129 | ~ if there was a problem with the command line arguments. 130 | 131 | 132 | # EXAMPLES 133 | 134 | Run **osmcoastline** on a planet file using default options: 135 | 136 | osmcoastline -o coastline.db planet.osm.pbf 137 | 138 | Running **osmcoastline_filter** first: 139 | 140 | osmcoastline_filter -o coastline.osm.pbf planet.osm.pbf 141 | osmcoastline -o coastline.db coastline.osm.pbf 142 | 143 | 144 | # SEE ALSO 145 | 146 | * `README.md` 147 | * **osmcoastline_filter**(1), **osmcoastline_readmeta**(1), 148 | **osmcoastline_segments**(1), **osmcoastline_ways**(1) 149 | * [Project page](https://osmcode.org/osmcoastline/) 150 | * [OSMCoastline in OSM wiki](https://wiki.openstreetmap.org/wiki/OSMCoastline) 151 | 152 | -------------------------------------------------------------------------------- /man/osmcoastline_filter.md: -------------------------------------------------------------------------------- 1 | 2 | # NAME 3 | 4 | osmcoastline_filter - filter coastline data from OpenStreetMap file 5 | 6 | 7 | # SYNOPSIS 8 | 9 | **osmcoastline_filter** \--output=*OUTPUT_FILE* *INPUT-FILE* 10 | 11 | **osmcoastline_filter** \--help 12 | 13 | 14 | # DESCRIPTION 15 | 16 | **osmcoastline_filter** is used to filter all nodes and ways needed for 17 | building the coastlines from an OSM planet. The data is written to the 18 | output file in PBF format. 19 | 20 | This output file will be a lot smaller (less than 1%) than the original planet 21 | file, but it contains everything needed to assemble the coastline and land 22 | or water polygons. 23 | 24 | If you are playing around or want to run **osmcoastline** several times with 25 | different parameters, run **osmcoastline_filter** once first and use its output 26 | as the input for **osmcoastline**. 27 | 28 | **osmcoastline_filter** can read any file format supported by libosmium (in 29 | particular this is PBF, XML, and OPL files), but write only PBF files. 30 | PBF files are much smaller and faster to read and write than XML files. The 31 | output file will first contain all ways tagged "natural=coastline", then all 32 | nodes used for those ways (and all nodes tagged "natural=coastline"). Having 33 | the ways first and the nodes later in the file is unusual for OSM files, but 34 | the **osmcoastline** and **osmcoastline_ways** programs work fine with it. 35 | 36 | 37 | # OPTIONS 38 | 39 | -f, \--format 40 | : Set output format options (default: pbf). 41 | 42 | -h, \--help 43 | : Display usage information and exit. 44 | 45 | -o, \--output=OSMFILE 46 | : Where to write output (default: none). 47 | 48 | -v, \--verbose 49 | : Enable verbose output. 50 | 51 | -V, \--version 52 | : Display program version and license information. 53 | 54 | 55 | # EXAMPLES 56 | 57 | Run it as follows: 58 | 59 | osmcoastline_filter -o coastline-data.osm.pbf planet.osm.pbf 60 | 61 | 62 | # SEE ALSO 63 | 64 | * **osmcoastline**(1), **osmcoastline_ways**(1) 65 | * [Project page](https://osmcode.org/osmcoastline/) 66 | * [OSMCoastline in OSM wiki](https://wiki.openstreetmap.org/wiki/OSMCoastline) 67 | 68 | -------------------------------------------------------------------------------- /man/osmcoastline_readmeta.md: -------------------------------------------------------------------------------- 1 | 2 | # NAME 3 | 4 | osmcoastline_readmeta - display metadata from database created by osmcoastline 5 | 6 | 7 | # SYNOPSIS 8 | 9 | **osmcoastline_readmeta** \[*COASTLINE-DB*\] 10 | 11 | 12 | # OPTIONS 13 | 14 | -h, \--help 15 | : Display usage information. 16 | 17 | -V, \--version 18 | : Display program version and license information. 19 | 20 | 21 | # DESCRIPTION 22 | 23 | This program displays various meta data from a database created by the 24 | **osmcoastline** program. 25 | 26 | If no database name is specified on the command line "testdata.db" is used. 27 | 28 | Displayed are the command line options used to create the database, some 29 | statistics about the coastline, the number of warnings and errors detected 30 | and more. 31 | 32 | This will only work if the (default) "SQLite" output format has been used 33 | on the **osmcoastline** program. 34 | 35 | 36 | # SEE ALSO 37 | 38 | * **osmcoastline**(1) 39 | * [Project page](https://osmcode.org/osmcoastline/) 40 | * [OSMCoastline in OSM wiki](https://wiki.openstreetmap.org/wiki/OSMCoastline) 41 | 42 | -------------------------------------------------------------------------------- /man/osmcoastline_segments.md: -------------------------------------------------------------------------------- 1 | 2 | # NAME 3 | 4 | osmcoastline_segments - analyze OpenStreetMap coastline changes from segment files 5 | 6 | 7 | # SYNOPSIS 8 | 9 | **osmcoastline_segments** \[*OPTIONS*\] *SEGMENT-FILE1* *SEGMENT-FILE2* 10 | 11 | 12 | # DESCRIPTION 13 | 14 | The **osmcoastline** program can write out all segments of the coastline into a 15 | file (when started with the **-S, \--write-segments=FILE** option). This 16 | program can be used to compare two of those segment files in various ways to 17 | detect coastline changes between different runs of the **osmcoastline** 18 | program. 19 | 20 | 21 | # OPTIONS 22 | 23 | -h, \--help 24 | : Display usage information. 25 | 26 | -d, \--dump 27 | : Dump segment list to stdout in plain text format. 28 | 29 | -g, \--geom=FILENAME 30 | : Write segments to geometry file or database using OGR. 31 | 32 | -f, \--format=FORMAT 33 | : OGR format for writing out geometries. 34 | 35 | -V, \--version 36 | : Display program version and license information. 37 | 38 | 39 | # DIAGNOSTICS 40 | 41 | **osmcoastline_segments** exits with exit code 42 | 43 | 0 44 | ~ if the segment files are the same 45 | 46 | 1 47 | ~ if the segment files are different 48 | 49 | 3 50 | ~ if there was a fatal error when running the program 51 | 52 | 4 53 | ~ if there was a problem with the command line arguments. 54 | 55 | 56 | # EXAMPLES 57 | 58 | Just return 0 or 1 depending on whether the segments are the same in both files: 59 | 60 | osmcoastline_segments old.segments new.segments 61 | 62 | Dump the list of removed and added segments to stdout: 63 | 64 | osmcoastline_segments --dump old.segments new.segments 65 | 66 | Create a shapefile with the differences: 67 | 68 | osmcoastline_segments --geom=diff.shp old.segments new.segments 69 | 70 | 71 | # SEE ALSO 72 | 73 | * `README.md` 74 | * **osmcoastline**(1) 75 | * [Project page](https://osmcode.org/osmcoastline/) 76 | * [OSMCoastline in OSM wiki](https://wiki.openstreetmap.org/wiki/OSMCoastline) 77 | 78 | -------------------------------------------------------------------------------- /man/osmcoastline_ways.md: -------------------------------------------------------------------------------- 1 | 2 | # NAME 3 | 4 | osmcoastline_ways - extract coastline ways from OpenStreetMap data 5 | 6 | 7 | # SYNOPSIS 8 | 9 | **osmcoastline_ways** *INPUT-FILE* \[*OUTPUT-DB*\] 10 | 11 | 12 | # OPTIONS 13 | 14 | -h, \--help 15 | : Display usage information. 16 | 17 | -V, \--version 18 | : Display program version and license information. 19 | 20 | 21 | # DESCRIPTION 22 | 23 | **osmcoastline_ways** extracts coastline ways from OSM data and writes them 24 | into a Spatialite database. The output data is meant for debugging and 25 | statistics. Use the **osmcoastline** program to assemble coastlines for 26 | "real" use. 27 | 28 | You have to run **osmcoastline_ways** on the output of the 29 | **osmcoastline_filter** program, otherwise it will not work correctly! 30 | 31 | The data is written to the Spatialite database *OUTPUT-DB*. If *OUTPUT-DB* is 32 | not set, the default "coastline-ways.db" is used. A single table "ways" is 33 | written. It contains the IDs of all ways, their geometries, and the contents 34 | of their "name" and "source" tags. 35 | 36 | **osmcoastline_ways** outputs the sum of all way lengths. 37 | 38 | 39 | # SEE ALSO 40 | 41 | * **osmcoastline**(1), **osmcoastline_filter**(1) 42 | * [Project page](https://osmcode.org/osmcoastline/) 43 | * [OSMCoastline in OSM wiki](https://wiki.openstreetmap.org/wiki/OSMCoastline) 44 | 45 | -------------------------------------------------------------------------------- /osmcoastline_readmeta: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # osmcoastline_readmeta [COASTLINEDB] 4 | # 5 | 6 | set -euo pipefail 7 | 8 | OSMCOASTLINE_VERSION=@PROJECT_VERSION@ 9 | 10 | SQLEXEC="sqlite3" 11 | 12 | if [[ $# -ge 1 && ( $1 == "--help" || $1 == "-h" ) ]]; then 13 | echo "Usage: osmcoastline_readmeta [COASTLINEDB]" 14 | exit 0 15 | fi 16 | 17 | if [[ $# -ge 1 && ( $1 == "--version" || $1 == "-v" ) ]]; then 18 | echo "osmcoastline_readmeta version $OSMCOASTLINE_VERSION" 19 | echo "Copyright (C) 2012-2025 Jochen Topf " 20 | echo "License: GNU GENERAL PUBLIC LICENSE Version 3 ." 21 | echo "This is free software: you are free to change and redistribute it." 22 | echo "There is NO WARRANTY, to the extent permitted by law."; 23 | exit 0 24 | fi 25 | 26 | if [[ $# -eq 0 ]]; then 27 | DBFILE=testdata.db 28 | else 29 | DBFILE=$1 30 | fi 31 | 32 | if [[ ! -e "$DBFILE" ]]; then 33 | echo "Can't open '$DBFILE'" 34 | exit 1 35 | fi 36 | 37 | function sql { 38 | $SQLEXEC -column "$DBFILE" | sed -e 's/^/ /' 39 | } 40 | 41 | function sql_select { 42 | echo -n " $1: " 43 | $SQLEXEC "$DBFILE" "$2" 44 | } 45 | 46 | function sql_meta { 47 | sql_select "$1" "SELECT $2 FROM meta;" 48 | } 49 | 50 | function sql_options { 51 | sql_select "$1" "SELECT $2 FROM options;" 52 | } 53 | 54 | printf "Options used to create this data:\n\n" 55 | 56 | sql_options "Overlap (--bbox-overlap/-b)" overlap 57 | sql_options "Close gaps in coastline smaller than (--close-distance/-c)" close_distance 58 | sql_options "Max points in polygons (--max-points/-m)" max_points_in_polygons 59 | sql_options "Split large polygons" "CASE split_large_polygons WHEN 0 THEN 'no' ELSE 'yes' END" 60 | 61 | sql_select "Spatial reference system (--srid/-s)" "SELECT CASE srid WHEN 4326 THEN '4326 (WGS84)' WHEN 3857 THEN '3857 (Mercator)' ELSE srid END FROM geometry_columns WHERE f_table_name='land_polygons';" 62 | 63 | printf "\nMetadata:\n\n" 64 | 65 | sql_meta "Database created at" timestamp 66 | sql_meta "Runtime (minutes)" "CAST(round(CAST(runtime AS REAL)/60) AS INT)" 67 | sql_meta "Memory usage (MB)" memory_usage 68 | sql_meta "Ways tagged natural=coastline" num_ways 69 | sql_meta "Number of nodes where coastline is not closed (before fixing)" num_unconnected_nodes 70 | sql_meta "Coastline rings" num_rings 71 | sql_meta "Coastline rings created from a single way" num_rings_from_single_way 72 | sql_meta "Coastline rings created from more then one way" "num_rings - num_rings_from_single_way" 73 | sql_meta "Number of rings fixed (closed)" num_rings_fixed 74 | sql_meta "Number of rings turned around" num_rings_turned_around 75 | sql_meta "Number of land polygons before split" num_land_polygons_before_split 76 | sql_meta "Number of land polygons after split" "CASE num_land_polygons_after_split WHEN 0 THEN 'NOT SPLIT' ELSE num_land_polygons_after_split END" 77 | 78 | printf "\nErrors/warnings (Points):\n\n" 79 | printf ".width 3 20\nSELECT count(*), error FROM error_points GROUP BY error;" | sql 80 | 81 | printf "\nErrors/warnings (LineStrings):\n\n" 82 | printf ".width 3 20\nSELECT count(*), error FROM error_lines GROUP BY error;" | sql 83 | 84 | printf "\nOutput:\n\n" 85 | echo "SELECT count(*), 'land_polygons' FROM land_polygons;" | sql 86 | echo "SELECT count(*), 'water_polygons' FROM water_polygons;" | sql 87 | echo "SELECT count(*), 'lines' FROM lines;" | sql 88 | echo "SELECT count(*), 'rings' FROM rings;" | sql 89 | 90 | echo 91 | 92 | -------------------------------------------------------------------------------- /render_image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # render_image.sh [DBFILE] 4 | # 5 | # Render polygons in DBFILE using shp2img program from mapserver. 6 | # Automatically detects SRID and renders accordingly. 7 | # If no DBFILE is given, testdata.db is used. 8 | # 9 | 10 | WIDTH=2048 11 | 12 | if [ "x$1" = "x" ]; then 13 | DBFILE=testdata.db 14 | else 15 | DBFILE=$1 16 | fi 17 | 18 | SRID=`sqlite3 $DBFILE "SELECT srid FROM geometry_columns WHERE f_table_name='polygons'"` 19 | 20 | if [ "$SRID" = "4326" ]; then 21 | # If SRID is WGS84 the image height is half its width 22 | HEIGHT=`expr $WIDTH / 2` 23 | EXTENT="-180 -90 180 90" 24 | else 25 | # If SRID is 3857 (Mercator) the image height is the same as its width 26 | HEIGHT=$WIDTH 27 | EXTENT="-20037508.342789244 -20037508.342789244 20037508.342789244 20037508.342789244" 28 | fi 29 | 30 | rm -f coastline-mapserver.db 31 | ln -s $DBFILE coastline-mapserver.db 32 | shp2img -m coastline.map -l polygons -i PNG -o polygons.png -s $WIDTH $HEIGHT -e $EXTENT 33 | rm -f coastline-mapserver.db 34 | 35 | -------------------------------------------------------------------------------- /runtest.sh.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "x$1" = "x-v" ]; then 4 | osmcoastline_valgrind="valgrind --leak-check=full --show-reachable=yes" 5 | fi 6 | 7 | exec $osmcoastline_valgrind src/osmcoastline --debug --verbose --overwrite --output-lines --output-polygons=both --output-rings -o testdata.db @PROJECT_SOURCE_DIR@/testdata.osm 8 | 9 | -------------------------------------------------------------------------------- /simplify_and_split_postgis/README: -------------------------------------------------------------------------------- 1 | 2 | HOW TO SIMPLIFY AND SPLIT LAND/WATER POLYGONS 3 | ============================================= 4 | 5 | The osmcoastline program already has built-in support for splitting land and 6 | water polygons created from the coastline. But it is fairly limited in what it 7 | can do. This is fine for many use cases, but if you want something different 8 | here are some tips and code that helps you with polygon simplification (for 9 | lower zoom levels) and with doing different kinds of splits using a 10 | PostgreSQL/PostGIS database. You should be somewhat familiar with PostGIS 11 | before attempting this. 12 | 13 | Note that the polygon simplification described here is done simply to make the 14 | geometries smaller and the polygon therefore faster to render. For proper 15 | generalization to create nice cartography, this is not the right process. See 16 | http://www.imagico.de/map/coastline_en.php for more about this. 17 | 18 | The different *.sql files in this directory run different steps of the process 19 | and they can be parameterized in different ways. Depending on what you want to 20 | do you have to run some or all of these SQL scripts, some maybe several times 21 | with different parameters. The *.sql files all have some documentation at the 22 | top, read it. 23 | 24 | Note that some of these steps can take a long time! 25 | 26 | Here is a step-by-step guide with an example. You can change the zoom levels, 27 | tolerances, etc.: 28 | 29 | 1. Create PostgreSQL database with PostGIS extensions, this description assumes 30 | the database is called "coastlines". 31 | 32 | 2. Create complete (non-split) land polygons in Mercator (3857) projection 33 | with osmcoastline or download them from 34 | http://data.openstreetmapdata.com/land-polygons-complete-3857.zip . 35 | The following assumes you have downloaded this file. 36 | 37 | 3. Load data into PostGIS database: 38 | 39 | # unzip land-polygons-complete-3857.zip 40 | # cd land-polygons-complete-3857 41 | # shp2pgsql -s 3857 -S -I land_polygons.shp | psql -d coastlines 42 | 43 | 4. Prepare some tables: 44 | 45 | # psql -d coastlines -f setup_tables.sql 46 | 47 | 5. For every zoom level you want, you have to prepare tile tables: 48 | 49 | # psql -d coastlines -v zoom=3 -f setup_bbox_tiles.sql 50 | # psql -d coastlines -v zoom=5 -f setup_bbox_tiles.sql 51 | # psql -d coastlines -v zoom=6 -f setup_bbox_tiles.sql 52 | 53 | 6. For every simplification step you need, do the simplification: 54 | 55 | # psql -d coastlines -v tolerance=3000 -v min_area=3000000 -f simplify_land_polygons.sql 56 | # psql -d coastlines -v tolerance=300 -v min_area=300000 -f simplify_land_polygons.sql 57 | 58 | 7. For every zoom level and simplification step you need you can create the 59 | final split land polygons: 60 | 61 | # psql -d coastlines -v tolerance=3000 -v min_area=3000000 -v zoom=3 -f split_land_polygons.sql 62 | # psql -d coastlines -v tolerance=300 -v min_area=300000 -v zoom=5 -f split_land_polygons.sql 63 | 64 | Instead of splitting the non-split polygons for each zoom level, you can use 65 | the already split polygons for smaller zoom levels and split them again for 66 | larger zoom levels. This is probably faster than always starting from the 67 | non-split polygons: 68 | 69 | # psql -d coastlines -v tolerance=300 -v min_area=300000 -v from_zoom=5 -v to_zoom=6 -f split_tiles.sql 70 | 71 | 8. After all split polygons for land areas are created you can call create the 72 | water polygons from them. Unlike the other scripts, this doesn't take any 73 | parameters, it will just create a water polygon for every split land polygon 74 | regardless of how the land polygon was created. 75 | 76 | # psql -d coastlines -f create_water_polygons.sql 77 | 78 | Note that splitting Polygons can lead to MultiPolygons, so some tables use 79 | MultiPolygon geometries. It is probably better for rendering to split 80 | multipolygons into polygons again. You can use the function ST_Dump() for this. 81 | 82 | Also note that sometimes splitting (Multi)Polygons can lead to non-polygon 83 | geometry types such as lines or points, this code will just remove those 84 | cases. 85 | 86 | The tables and SQL queries in these files are crafted in a way that allows any 87 | number of different simplification parameters and any number of different 88 | splittings based on different zoom levels in the same tables. The parameters 89 | "tolerance", "min_area" and "zoom" will appear in all tables as columns to 90 | allow you to distinguish which geometry belongs to which. In addition the "x" 91 | and "y" columns give you the tile numbers a specific geometry is for. This may 92 | or may not be the most efficient way of doing this, so you might want to 93 | optimize data layout and especially index creation for your use case, but it is 94 | a flexible starting point for your own experiments. 95 | 96 | -------------------------------------------------------------------------------- /simplify_and_split_postgis/create_water_polygons.sql: -------------------------------------------------------------------------------- 1 | -- ====================================================================== 2 | -- Create water polygons 3 | -- 4 | -- Creates split water polygons from all split land polygons. 5 | -- 6 | -- Call like this: 7 | -- psql -d coastlines -f create_water_polygons.sql 8 | -- 9 | -- ====================================================================== 10 | 11 | -- This is a helper view that makes the following INSERT easier to write. 12 | -- For every tile this view contains the union of all land polygons in that tile. 13 | CREATE VIEW merged_land_polygons AS 14 | SELECT tolerance, min_area, zoom, x, y, ST_Union(geom) AS geom 15 | FROM split_land_polygons p 16 | GROUP BY tolerance, min_area, zoom, x, y; 17 | 18 | INSERT INTO split_water_polygons (tolerance, min_area, zoom, x, y, geom) 19 | SELECT p.tolerance, p.min_area, p.zoom, p.x, p.y, ST_Multi(ST_Difference(b.geom, p.geom)) 20 | FROM merged_land_polygons p, bbox_tiles b 21 | WHERE p.zoom = b.zoom 22 | AND p.x = b.x 23 | AND p.y = b.y; 24 | 25 | -- If there are no land polygons in a tile, no water polygon will be created for it. 26 | -- This INSERT adds the water polygons in that case. 27 | INSERT INTO split_water_polygons (tolerance, min_area, zoom, x, y, geom) 28 | SELECT p.tolerance, p.min_area, x, y, b.zoom, ST_Multi(geom) 29 | FROM bbox_tiles b, (SELECT distinct tolerance, min_area, zoom from split_land_polygons) p 30 | WHERE b.zoom=p.zoom 31 | AND x || '-' || y || '-' || b.zoom NOT IN (SELECT x || '-' || y || '-' || zoom FROM split_land_polygons); 32 | 33 | DROP VIEW merged_land_polygons; 34 | 35 | CREATE INDEX 36 | ON split_water_polygons 37 | USING GIST (geom); 38 | 39 | -------------------------------------------------------------------------------- /simplify_and_split_postgis/setup_bbox_tiles.sql: -------------------------------------------------------------------------------- 1 | -- ====================================================================== 2 | -- 3 | -- Set up helper table with polygons containing the bbox for each tile 4 | -- 5 | -- Call like this: 6 | -- psql -d coastlines -v zoom=$ZOOM -f setup_bbox_tiles.sql 7 | -- 8 | -- for instance: 9 | -- psql -d coastlines -v zoom=3 -f setup_bbox_tiles.sql 10 | -- 11 | -- ====================================================================== 12 | 13 | -- max value for x or y coordinate in spherical mercator 14 | \set cmax 20037508.34 15 | \set pow (2.0 ^ :zoom)::integer 16 | \set tilesize (2*:cmax / :pow) 17 | 18 | CREATE VIEW tiles AS SELECT * FROM generate_series(0, :pow - 1) AS x, generate_series(0, :pow - 1) AS y; 19 | CREATE VIEW bbox AS SELECT :zoom AS zoom, x, y, x*:tilesize - :cmax AS x1, (:pow - y - 1)*:tilesize - :cmax AS y1, (x+1)*:tilesize - :cmax AS x2, (:pow - y)*:tilesize - :cmax AS y2 FROM tiles; 20 | 21 | INSERT INTO bbox_tiles (zoom, x, y, geom) 22 | SELECT zoom, x, y, ST_SetSRID(ST_MakeBox2D(ST_MakePoint(x1, y1), ST_MakePoint(x2, y2)), 3857) AS geom 23 | FROM bbox; 24 | 25 | DROP VIEW bbox; 26 | DROP VIEW tiles; 27 | 28 | -------------------------------------------------------------------------------- /simplify_and_split_postgis/setup_tables.sql: -------------------------------------------------------------------------------- 1 | -- ====================================================================== 2 | -- Setup tables 3 | -- ====================================================================== 4 | 5 | DROP TABLE IF EXISTS simplified_land_polygons; 6 | CREATE TABLE simplified_land_polygons ( 7 | id SERIAL, 8 | fid INT, 9 | tolerance INT, 10 | min_area INT 11 | ); 12 | 13 | SELECT AddGeometryColumn('simplified_land_polygons', 'geom', 3857, 'POLYGON', 2); 14 | 15 | 16 | DROP TABLE IF EXISTS split_land_polygons; 17 | CREATE TABLE split_land_polygons ( 18 | id SERIAL, 19 | fid INT, 20 | tolerance INT, 21 | min_area INT, 22 | zoom INT, 23 | x INT, 24 | y INT 25 | ); 26 | 27 | -- splitting can make multipolygons out of polygons, so geometry type is different 28 | SELECT AddGeometryColumn('split_land_polygons', 'geom', 3857, 'MULTIPOLYGON', 2); 29 | 30 | 31 | DROP TABLE IF EXISTS split_water_polygons; 32 | CREATE TABLE split_water_polygons ( 33 | id SERIAL, 34 | tolerance INT, 35 | min_area INT, 36 | zoom INT, 37 | x INT, 38 | y INT 39 | ); 40 | 41 | SELECT AddGeometryColumn('split_water_polygons', 'geom', 3857, 'MULTIPOLYGON', 2); 42 | 43 | 44 | DROP TABLE IF EXISTS bbox_tiles; 45 | CREATE TABLE bbox_tiles ( 46 | id SERIAL, 47 | zoom INT, 48 | x INT, 49 | y INT 50 | ); 51 | 52 | SELECT AddGeometryColumn('bbox_tiles', 'geom', 3857, 'POLYGON', 2); 53 | 54 | CREATE INDEX idx_bbox_files_geom ON bbox_tiles USING GIST (geom); 55 | 56 | -------------------------------------------------------------------------------- /simplify_and_split_postgis/simplify_land_polygons.sql: -------------------------------------------------------------------------------- 1 | -- ====================================================================== 2 | -- 3 | -- Simplify land polygons 4 | -- 5 | -- Parameters: 6 | -- tolerance: Tolerance for simplification algorithm, higher values 7 | -- mean more simplification 8 | -- min_area: Polygons with smaller area than this will not appear 9 | -- in the output 10 | -- 11 | -- Call like this: 12 | -- psql -d $DB -v tolerance=$TOLERANCE -v min_area=$MIN_AREA -f simplify_land_polygons.sql 13 | -- 14 | -- for instance: 15 | -- psql -d coastlines -v tolerance=300 -v min_area=300000 -f simplify_land_polygons.sql 16 | -- 17 | -- ====================================================================== 18 | 19 | INSERT INTO simplified_land_polygons (fid, tolerance, min_area, geom) 20 | SELECT fid, :tolerance, :min_area, ST_SimplifyPreserveTopology(geom, :tolerance) 21 | FROM land_polygons 22 | WHERE ST_Area(geom) > :min_area; 23 | 24 | CREATE INDEX 25 | ON simplified_land_polygons 26 | USING GIST (geom) 27 | WHERE tolerance=:tolerance AND min_area=:min_area; 28 | 29 | -------------------------------------------------------------------------------- /simplify_and_split_postgis/split_land_polygons.sql: -------------------------------------------------------------------------------- 1 | -- ====================================================================== 2 | -- 3 | -- Split land polygons into tiles 4 | -- 5 | -- This can either split non-simplified land polygons or simplified land 6 | -- polygons. Set tolerance=0 and min_area=0 to use non-simplified land 7 | -- polygons, otherwise the simplified polygons with the given parameters 8 | -- are used. 9 | -- 10 | -- Call like this: 11 | -- psql -d $DB -v tolerance=$TOLERANCE -v min_area=$MIN_AREA -v zoom=$ZOOM -f split_land_polygons.sql 12 | -- 13 | -- for instance: 14 | -- psql -d coastlines -v tolerance=300 -v min_area=300000 -v zoom=3 -f split_land_polygons.sql 15 | -- 16 | -- ====================================================================== 17 | 18 | -- Only one of the following INSERT statements will do anything thanks to the 19 | -- last condition (:tolerance (!)=0). 20 | 21 | -- case 1: split non-simplified polygons 22 | INSERT INTO split_land_polygons (fid, tolerance, min_area, zoom, x, y, geom) 23 | SELECT p.fid, 0, 0, b.zoom, b.x, b.y, ST_Multi(ST_Intersection(p.geom, b.geom)) 24 | FROM land_polygons p, bbox_tiles b 25 | WHERE p.geom && b.geom 26 | AND ST_Intersects(p.geom, b.geom) 27 | AND b.zoom=:zoom 28 | AND ST_GeometryType(ST_Multi(ST_Intersection(p.geom, b.geom))) = 'ST_MultiPolygon' 29 | AND :tolerance=0; 30 | 31 | -- case 2: split simplified polygons 32 | INSERT INTO split_land_polygons (fid, tolerance, min_area, zoom, x, y, geom) 33 | SELECT p.fid, p.tolerance, p.min_area, b.zoom, b.x, b.y, ST_Multi(ST_Intersection(p.geom, b.geom)) 34 | FROM simplified_land_polygons p, bbox_tiles b 35 | WHERE p.geom && b.geom 36 | AND ST_Intersects(p.geom, b.geom) 37 | AND p.tolerance=:tolerance 38 | AND p.min_area=:min_area 39 | AND b.zoom=:zoom 40 | AND ST_GeometryType(ST_Multi(ST_Intersection(p.geom, b.geom))) = 'ST_MultiPolygon' 41 | AND :tolerance!=0; 42 | 43 | CREATE INDEX 44 | ON split_land_polygons 45 | USING GIST (geom) 46 | WHERE tolerance=:tolerance 47 | AND min_area=:min_area 48 | AND zoom=:zoom; 49 | 50 | -------------------------------------------------------------------------------- /simplify_and_split_postgis/split_tiles.sql: -------------------------------------------------------------------------------- 1 | -- ====================================================================== 2 | -- 3 | -- Split land polygon tiles into smaller tiles 4 | -- 5 | -- Call like this: 6 | -- psql -d $DB -v tolerance=$TOLERANCE -v min_area=$MIN_AREA -v from_zoom=$FROM_ZOOM -v to_zoom=$TO_ZOOM -f split_tiles.sql 7 | -- 8 | -- for instance: 9 | -- psql -d coastlines -v tolerance=0 -v min_area=0 -v from_zoom=5 -v to_zoom=6 -f split_tiles.sql 10 | -- 11 | -- ====================================================================== 12 | 13 | INSERT INTO split_land_polygons (fid, tolerance, min_area, zoom, x, y, geom) 14 | SELECT p.fid, :tolerance, :min_area, :to_zoom, b.x, b.y, ST_Multi(ST_Intersection(p.geom, b.geom)) 15 | FROM split_land_polygons p, bbox_tiles b 16 | WHERE ST_Intersects(p.geom, b.geom) 17 | AND p.tolerance=:tolerance 18 | AND p.min_area=:min_area 19 | AND p.zoom=:from_zoom 20 | AND b.zoom=:to_zoom 21 | AND ST_GeometryType(ST_Multi(ST_Intersection(p.geom, b.geom))) = 'ST_MultiPolygon'; 22 | 23 | -------------------------------------------------------------------------------- /simplify_and_split_spatialite/create_grid_3857.sql: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------ 2 | -- 3 | -- Create grid for splitting up Mercator polygons. 4 | -- 5 | ------------------------------------------------------------------------------ 6 | 7 | -- speed up spatialite processing 8 | PRAGMA journal_mode = OFF; 9 | PRAGMA synchronous = OFF; 10 | PRAGMA temp_store = MEMORY; 11 | PRAGMA cache_size = 5000000; 12 | 13 | -- make sure we have the SRID in the spatial_ref_sys table 14 | SELECT InsertEpsgSrid(3857) WHERE (SELECT count(*) FROM spatial_ref_sys WHERE srid = 3857) = 0; 15 | 16 | -- clean up if this is left over from previous run 17 | SELECT DiscardGeometryColumn('grid', 'geometry') FROM geometry_columns WHERE f_table_name='grid'; 18 | DROP TABLE IF EXISTS grid; 19 | 20 | CREATE TABLE tmp_square_grid (id INTEGER PRIMARY KEY AUTOINCREMENT); 21 | SELECT AddGeometryColumn('tmp_square_grid', 'geometry', 3857, 'MULTIPOLYGON', 'XY'); 22 | 23 | INSERT INTO tmp_square_grid (geometry) 24 | SELECT ST_SquareGrid( 25 | -- slightly smaller than the full extent so we don't get an 26 | -- extra set of tiles at the top and right boundary 27 | BuildMbr(-20037508.34, -20037508.34, 20037508.0, 20037508.0, 3857), 28 | -- split into 64x64 tiles 29 | 2*20037508.34/64); 30 | 31 | .elemgeo tmp_square_grid geometry tmp_square_grid_elem id_new id; 32 | 33 | CREATE TABLE grid (OGC_FID INTEGER PRIMARY KEY AUTOINCREMENT); 34 | SELECT AddGeometryColumn('grid', 'geometry', 3857, 'POLYGON', 'XY'); 35 | 36 | INSERT INTO grid (geometry) 37 | SELECT ST_Intersection( 38 | -- 50 units overlap on each side of the tile 39 | ST_Expand(geometry, 50.0), 40 | -- make sure there are no overlaps at the sides 41 | BuildMbr(-20037508.34, -20037508.34, 20037508.34, 20037508.34, 3857)) 42 | FROM tmp_square_grid_elem; 43 | 44 | SELECT CreateSpatialIndex('grid', 'geometry'); 45 | 46 | SELECT DiscardGeometryColumn('tmp_square_grid', 'geometry'); 47 | DROP TABLE tmp_square_grid; 48 | 49 | SELECT DiscardGeometryColumn('tmp_square_grid_elem', 'geometry'); 50 | DROP TABLE tmp_square_grid_elem; 51 | 52 | -------------------------------------------------------------------------------- /simplify_and_split_spatialite/create_grid_4326.sql: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------ 2 | -- 3 | -- Create grid for splitting up WGS84 polygons. 4 | -- 5 | ------------------------------------------------------------------------------ 6 | 7 | -- speed up spatialite processing 8 | PRAGMA journal_mode = OFF; 9 | PRAGMA synchronous = OFF; 10 | PRAGMA temp_store = MEMORY; 11 | PRAGMA cache_size = 5000000; 12 | 13 | -- make sure we have the SRID in the spatial_ref_sys table 14 | SELECT InsertEpsgSrid(4326) WHERE (SELECT count(*) FROM spatial_ref_sys WHERE srid = 4326) = 0; 15 | 16 | -- clean up if this is left over from previous run 17 | SELECT DiscardGeometryColumn('grid', 'geometry') FROM geometry_columns WHERE f_table_name='grid'; 18 | DROP TABLE IF EXISTS grid; 19 | 20 | CREATE TABLE tmp_square_grid (id INTEGER PRIMARY KEY AUTOINCREMENT); 21 | SELECT AddGeometryColumn('tmp_square_grid', 'geometry', 4326, 'MULTIPOLYGON', 'XY'); 22 | 23 | INSERT INTO tmp_square_grid (geometry) 24 | SELECT ST_SquareGrid(BuildMbr(-180.0, -90.0, 179.99999999, 89.99999999, 4326), 1); 25 | 26 | .elemgeo tmp_square_grid geometry tmp_square_grid_elem id_new id; 27 | 28 | CREATE TABLE grid (OGC_FID INTEGER PRIMARY KEY AUTOINCREMENT); 29 | SELECT AddGeometryColumn('grid', 'geometry', 4326, 'POLYGON', 'XY'); 30 | 31 | INSERT INTO grid (geometry) 32 | SELECT ST_Intersection( 33 | -- the grid cell overlap has different width depending on 34 | -- latitude, in the other direction it is always 0.0005 degrees 35 | BuildMbr(ST_MinX(geometry) - (0.0005 / cos(radians(0.5 * (ST_MinY(geometry) + ST_MaxY(geometry))))), 36 | ST_MinY(geometry) - 0.0005, 37 | ST_MaxX(geometry) + (0.0005 / cos(radians(0.5 * (ST_MinY(geometry) + ST_MaxY(geometry))))), 38 | ST_MaxY(geometry) + 0.0005, 4326), 39 | -- make sure there are no overlaps at the sides 40 | BuildMbr(-180.0, -90.0, 179.99999999, 89.99999999, 4326)) 41 | FROM tmp_square_grid_elem; 42 | 43 | SELECT CreateSpatialIndex('grid', 'geometry'); 44 | 45 | SELECT DiscardGeometryColumn('tmp_square_grid', 'geometry'); 46 | DROP TABLE tmp_square_grid; 47 | 48 | SELECT DiscardGeometryColumn('tmp_square_grid_elem', 'geometry'); 49 | DROP TABLE tmp_square_grid_elem; 50 | 51 | -------------------------------------------------------------------------------- /simplify_and_split_spatialite/simplify.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- simplify.sql 3 | -- 4 | -- You can use this to simplify coastline polygons if needed. 5 | -- It will only work properly if polygons are not split and 6 | -- the 3857 projection is used (ie if osmcoastline was run with 7 | -- "-m 0 -s 3857"). 8 | -- 9 | 10 | CREATE TABLE simplified_land_polygons ( 11 | OGC_FID INTEGER PRIMARY KEY 12 | ); 13 | 14 | SELECT AddGeometryColumn('simplified_land_polygons', 'GEOMETRY', 3857, 'POLYGON', 'XY', 1); 15 | 16 | -- Depending on the detail you need you might have to change the numbers here. 17 | -- The given numbers work up to about zoom level 9. 18 | INSERT INTO simplified_land_polygons (ogc_fid, geometry) SELECT ogc_fid, SimplifyPreserveTopology(geometry, 300) FROM land_polygons WHERE ST_Area(geometry) > 300000; 19 | 20 | SELECT CreateSpatialIndex('simplified_land_polygons', 'GEOMETRY'); 21 | 22 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #----------------------------------------------------------------------------- 2 | # 3 | # CMake Config 4 | # 5 | # OSMCoastline 6 | # 7 | #----------------------------------------------------------------------------- 8 | 9 | add_executable(osmcoastline 10 | osmcoastline.cpp coastline_ring.cpp coastline_ring_collection.cpp coastline_polygons.cpp output_database.cpp srs.cpp options.cpp 11 | ${PROJECT_BINARY_DIR}/src/version.cpp) 12 | 13 | target_link_libraries(osmcoastline ${OSMIUM_IO_LIBRARIES} ${GDAL_LIBRARIES} ${GEOS_C_LIBRARIES} ${GETOPT_LIBRARY}) 14 | set_pthread_on_target(osmcoastline) 15 | install(TARGETS osmcoastline DESTINATION bin) 16 | 17 | add_executable(osmcoastline_filter osmcoastline_filter.cpp 18 | ${PROJECT_BINARY_DIR}/src/version.cpp) 19 | 20 | target_link_libraries(osmcoastline_filter ${OSMIUM_IO_LIBRARIES} ${GETOPT_LIBRARY}) 21 | set_pthread_on_target(osmcoastline_filter) 22 | install(TARGETS osmcoastline_filter DESTINATION bin) 23 | 24 | add_executable(osmcoastline_segments osmcoastline_segments.cpp srs.cpp 25 | ${PROJECT_BINARY_DIR}/src/version.cpp) 26 | 27 | target_link_libraries(osmcoastline_segments ${GDAL_LIBRARIES} ${GETOPT_LIBRARY}) 28 | install(TARGETS osmcoastline_segments DESTINATION bin) 29 | 30 | add_executable(osmcoastline_ways osmcoastline_ways.cpp return_codes.hpp 31 | ${PROJECT_BINARY_DIR}/src/version.cpp) 32 | 33 | target_link_libraries(osmcoastline_ways ${OSMIUM_IO_LIBRARIES} ${GDAL_LIBRARIES}) 34 | set_pthread_on_target(osmcoastline_ways) 35 | install(TARGETS osmcoastline_ways DESTINATION bin) 36 | 37 | # only used for testing - should not be installed 38 | add_executable(nodegrid2opl nodegrid2opl.cpp) 39 | 40 | -------------------------------------------------------------------------------- /src/coastline_polygons.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COASTLINE_POLYGONS_HPP 2 | #define COASTLINE_POLYGONS_HPP 3 | 4 | /* 5 | 6 | Copyright 2012-2025 Jochen Topf . 7 | 8 | This file is part of OSMCoastline. 9 | 10 | OSMCoastline is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | OSMCoastline is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with OSMCoastline. If not, see . 22 | 23 | */ 24 | 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | class OGRSpatialReference; 32 | class OutputDatabase; 33 | 34 | using polygon_vector_type = std::vector>; 35 | 36 | /** 37 | * A collection of land polygons created out of coastlines. 38 | * Contains operations for SRS transformation, splitting up of large polygons 39 | * and converting to water polygons. 40 | */ 41 | class CoastlinePolygons { 42 | 43 | /// Output database 44 | OutputDatabase& m_output; 45 | 46 | /** 47 | * When splitting polygons we want them to overlap slightly to avoid 48 | * rendering artefacts. This is the amount each geometry is expanded 49 | * in each direction. 50 | */ 51 | double m_expand; 52 | 53 | /** 54 | * When splitting polygons they are split until they have less than 55 | * this amount of points in them. 56 | */ 57 | int m_max_points_in_polygon; 58 | 59 | /** 60 | * Vector of polygons we want to operate on. This is initialized in 61 | * the constructor from the polygons created from coastline rings. 62 | * After that the different methods on this class will convert the 63 | * polygons and always leave the result in this vector again. 64 | */ 65 | polygon_vector_type m_polygons; 66 | 67 | OGREnvelope m_env_west; 68 | OGREnvelope m_env_east; 69 | 70 | /** 71 | * Max depth after recursive splitting. 72 | */ 73 | int m_max_split_depth = 0; 74 | 75 | void split_geometry(std::unique_ptr&& geom, int level); 76 | void split_polygon(std::unique_ptr&& polygon, int level); 77 | void split_bbox(const OGREnvelope& envelope, polygon_vector_type&& v); 78 | 79 | std::pair, std::unique_ptr> split_envelope(const OGREnvelope& envelope, int level, int num_points) const; 80 | 81 | #if (GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,7,0)) 82 | void add_line_to_output(std::unique_ptr line, const OGRSpatialReference* srs) const; 83 | #else 84 | void add_line_to_output(std::unique_ptr line, OGRSpatialReference* srs) const; 85 | #endif 86 | void output_polygon_ring_as_lines(int max_points, const OGRLinearRing* ring) const; 87 | 88 | public: 89 | 90 | CoastlinePolygons(polygon_vector_type&& polygons, OutputDatabase& output, double expand, int max_points_in_polygon) : 91 | m_output(output), 92 | m_expand(expand), 93 | m_max_points_in_polygon(max_points_in_polygon), 94 | m_polygons(std::move(polygons)) { 95 | } 96 | 97 | /// Number of polygons 98 | int num_polygons() const noexcept { 99 | return m_polygons.size(); 100 | } 101 | 102 | polygon_vector_type::const_iterator begin() const noexcept { 103 | return m_polygons.begin(); 104 | } 105 | 106 | polygon_vector_type::const_iterator end() const noexcept { 107 | return m_polygons.end(); 108 | } 109 | 110 | /// Turn polygons with wrong winding order around. 111 | unsigned int fix_direction(); 112 | 113 | /// Transform all polygons to output SRS. 114 | void transform(); 115 | 116 | /// Split up all polygons. 117 | void split(); 118 | 119 | /// Check polygons for validity and try to make them valid if needed 120 | unsigned int check_polygons(); 121 | 122 | /// Write all land polygons to the output database. 123 | void output_land_polygons(bool make_copy); 124 | 125 | /// Write all water polygons to the output database. 126 | void output_water_polygons(); 127 | 128 | /// Write all coastlines to the output database (as lines). 129 | void output_lines(int max_points) const; 130 | 131 | bool antarctica_bogus(const OGRGeometry* geom) noexcept; 132 | 133 | }; // class CoastlinePolygons 134 | 135 | #endif // COASTLINE_POLYGONS_HPP 136 | -------------------------------------------------------------------------------- /src/coastline_ring.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2012-2025 Jochen Topf . 4 | 5 | This file is part of OSMCoastline. 6 | 7 | OSMCoastline is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | OSMCoastline is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with OSMCoastline. If not, see . 19 | 20 | */ 21 | 22 | #include "coastline_ring.hpp" 23 | 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | void CoastlineRing::setup_locations(locmap_type& locmap) { 37 | for (auto& wn : m_way_node_list) { 38 | locmap.insert(std::make_pair(wn.ref(), &(wn.location()))); 39 | } 40 | } 41 | 42 | unsigned int CoastlineRing::check_locations(bool output_missing) { 43 | unsigned int missing_locations = 0; 44 | 45 | for (const auto& wn : m_way_node_list) { 46 | if (!wn.location()) { 47 | ++missing_locations; 48 | if (output_missing) { 49 | std::cerr << "Missing location of node " << wn.ref() << "\n"; 50 | } 51 | } 52 | } 53 | 54 | return missing_locations; 55 | } 56 | 57 | void CoastlineRing::add_at_front(const osmium::Way& way) { 58 | assert(first_node_id() == way.nodes().back().ref()); 59 | m_way_node_list.insert(m_way_node_list.begin(), way.nodes().begin(), way.nodes().end()-1); 60 | 61 | update_ring_id(way.id()); 62 | m_nways++; 63 | } 64 | 65 | void CoastlineRing::add_at_end(const osmium::Way& way) { 66 | assert(last_node_id() == way.nodes().front().ref()); 67 | m_way_node_list.insert(m_way_node_list.end(), way.nodes().begin()+1, way.nodes().end()); 68 | 69 | update_ring_id(way.id()); 70 | m_nways++; 71 | } 72 | 73 | void CoastlineRing::join(const CoastlineRing& other) { 74 | assert(last_node_id() == other.first_node_id()); 75 | m_way_node_list.insert(m_way_node_list.end(), other.m_way_node_list.begin()+1, other.m_way_node_list.end()); 76 | 77 | update_ring_id(other.ring_id()); 78 | m_nways += other.m_nways; 79 | } 80 | 81 | void CoastlineRing::join_over_gap(const CoastlineRing& other) { 82 | if (last_location() != other.first_location()) { 83 | m_way_node_list.push_back(other.m_way_node_list.front()); 84 | } 85 | 86 | m_way_node_list.insert(m_way_node_list.end(), other.m_way_node_list.begin()+1, other.m_way_node_list.end()); 87 | 88 | update_ring_id(other.ring_id()); 89 | m_nways += other.m_nways; 90 | m_fixed = true; 91 | } 92 | 93 | void CoastlineRing::close_ring() { 94 | if (first_location() != last_location()) { 95 | m_way_node_list.push_back(m_way_node_list.front()); 96 | } 97 | m_fixed = true; 98 | } 99 | 100 | void CoastlineRing::close_antarctica_ring(int epsg) { 101 | const double min = epsg == 4326 ? -90.0 : -85.0511288; 102 | 103 | for (int lat = -78; lat > static_cast(min); --lat) { 104 | m_way_node_list.emplace_back(0, osmium::Location{-180.0, static_cast(lat)}); 105 | } 106 | 107 | for (int lon = -180; lon < 180; ++lon) { 108 | m_way_node_list.emplace_back(0, osmium::Location{static_cast(lon), min}); 109 | } 110 | 111 | if (epsg == 3857) { 112 | m_way_node_list.emplace_back(0, osmium::Location{180.0, min}); 113 | } 114 | 115 | for (auto lat = static_cast(min); lat < -78; ++lat) { 116 | m_way_node_list.emplace_back(0, osmium::Location{180.0, static_cast(lat)}); 117 | } 118 | 119 | m_way_node_list.push_back(m_way_node_list.front()); 120 | m_fixed = true; 121 | } 122 | 123 | std::unique_ptr CoastlineRing::ogr_polygon(osmium::geom::OGRFactory<>& geom_factory, bool reverse) const { 124 | geom_factory.polygon_start(); 125 | std::size_t num_points = 0; 126 | if (reverse) { 127 | num_points = geom_factory.fill_polygon(m_way_node_list.crbegin(), m_way_node_list.crend()); 128 | } else { 129 | num_points = geom_factory.fill_polygon(m_way_node_list.cbegin(), m_way_node_list.cend()); 130 | } 131 | return geom_factory.polygon_finish(num_points); 132 | } 133 | 134 | std::unique_ptr CoastlineRing::ogr_linestring(osmium::geom::OGRFactory<>& geom_factory, bool reverse) const { 135 | geom_factory.linestring_start(); 136 | std::size_t num_points = 0; 137 | if (reverse) { 138 | num_points = geom_factory.fill_linestring(m_way_node_list.crbegin(), m_way_node_list.crend()); 139 | } else { 140 | num_points = geom_factory.fill_linestring(m_way_node_list.cbegin(), m_way_node_list.cend()); 141 | } 142 | return geom_factory.linestring_finish(num_points); 143 | } 144 | 145 | std::unique_ptr CoastlineRing::ogr_first_point() const { 146 | assert(!m_way_node_list.empty()); 147 | const osmium::NodeRef& node_ref = m_way_node_list.front(); 148 | return std::make_unique(node_ref.lon(), node_ref.lat()); 149 | } 150 | 151 | std::unique_ptr CoastlineRing::ogr_last_point() const { 152 | assert(!m_way_node_list.empty()); 153 | const osmium::NodeRef& node_ref = m_way_node_list.back(); 154 | return std::make_unique(node_ref.lon(), node_ref.lat()); 155 | } 156 | 157 | // Pythagoras doesn't work on a round earth but that is ok here, we only need a 158 | // rough measure anyway 159 | double CoastlineRing::distance_to_start_location(osmium::Location pos) const { 160 | assert(!m_way_node_list.empty()); 161 | const osmium::Location p = m_way_node_list.front().location(); 162 | return ((pos.lon() - p.lon()) * (pos.lon() - p.lon())) + 163 | ((pos.lat() - p.lat()) * (pos.lat() - p.lat())); 164 | } 165 | 166 | void CoastlineRing::add_segments_to_vector(std::vector& segments) const { 167 | if (m_way_node_list.size() > 1) { 168 | for (auto it = m_way_node_list.begin(); it != m_way_node_list.end() - 1; ++it) { 169 | segments.emplace_back(it->location(), (it+1)->location()); 170 | } 171 | } 172 | } 173 | 174 | std::ostream& operator<<(std::ostream& out, const CoastlineRing& cp) { 175 | out << "CoastlineRing(ring_id=" << cp.ring_id() 176 | << ", nways=" << cp.nways() 177 | << ", npoints=" << cp.npoints() 178 | << ", first_node_id=" << cp.first_node_id() 179 | << ", last_node_id=" << cp.last_node_id(); 180 | 181 | if (cp.is_closed()) { 182 | out << " [CLOSED]"; 183 | } 184 | 185 | out << ")"; 186 | return out; 187 | } 188 | 189 | -------------------------------------------------------------------------------- /src/coastline_ring.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COASTLINE_RING_HPP 2 | #define COASTLINE_RING_HPP 3 | 4 | /* 5 | 6 | Copyright 2012-2025 Jochen Topf . 7 | 8 | This file is part of OSMCoastline. 9 | 10 | OSMCoastline is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | OSMCoastline is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with OSMCoastline. If not, see . 22 | 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | class OGRPoint; 36 | class OGRLineString; 37 | class OGRPolygon; 38 | 39 | using locmap_type = std::multimap; 40 | 41 | /** 42 | * The CoastlineRing class models a (possibly unfinished) ring of 43 | * coastline, ie. a closed list of points. 44 | * 45 | * CoastlineRing objects are created from a way. If the way is 46 | * already closed we are done. If not, more ways will be added 47 | * later until the ring is closed. 48 | * 49 | * The CoastlineRing keeps track of all the node IDs needed for 50 | * the ring. 51 | * 52 | * To get a unique ID for the coastline ring, the minimum way 53 | * ID is also kept. 54 | * 55 | * By definition coastlines in OSM are tagged as natural=coastline 56 | * and the land is always to the *left* of the way, the water to 57 | * the right. So a ring around an island is going counter-clockwise. 58 | * In normal GIS use, outer rings of polygons are often expected 59 | * to be clockwise, and inner rings are count-clockwise. So, if we 60 | * are describing land polygons, the OSM convention is just the 61 | * opposite of the GIS convention. Because of this the methods 62 | * on CoastlineRing that create OGR geometries will actually turn 63 | * the rings around. 64 | */ 65 | class CoastlineRing { 66 | 67 | std::vector m_way_node_list{}; 68 | 69 | /** 70 | * Smallest ID of all the ways making up the ring. Can be used as somewhat 71 | * stable unique ID for the ring. 72 | */ 73 | osmium::object_id_type m_ring_id; 74 | 75 | /** 76 | * The number of ways making up this ring. This is not actually needed for 77 | * anything, but kept to create statistics. 78 | */ 79 | unsigned int m_nways = 1; 80 | 81 | /// Ring was fixed because of missing/wrong OSM data. 82 | bool m_fixed = false; 83 | 84 | /// Is this an outer ring? 85 | bool m_outer = false; 86 | 87 | public: 88 | 89 | /** 90 | * Create CoastlineRing from a way. 91 | */ 92 | explicit CoastlineRing(const osmium::Way& way) : 93 | m_ring_id(way.id()) { 94 | assert(!way.nodes().empty()); 95 | m_way_node_list.reserve(way.is_closed() ? way.nodes().size() : 1000); 96 | m_way_node_list.insert(m_way_node_list.begin(), way.nodes().begin(), way.nodes().end()); 97 | } 98 | 99 | bool is_outer() const noexcept { 100 | return m_outer; 101 | } 102 | 103 | void set_outer() noexcept { 104 | m_outer = true; 105 | } 106 | 107 | /// ID of first node in the ring. 108 | osmium::object_id_type first_node_id() const noexcept { 109 | assert(!m_way_node_list.empty()); 110 | return m_way_node_list.front().ref(); 111 | } 112 | 113 | /// ID of last node in the ring. 114 | osmium::object_id_type last_node_id() const noexcept { 115 | assert(!m_way_node_list.empty()); 116 | return m_way_node_list.back().ref(); 117 | } 118 | 119 | /// Location of the first node in the ring. 120 | osmium::Location first_location() const noexcept { 121 | assert(!m_way_node_list.empty()); 122 | return m_way_node_list.front().location(); 123 | } 124 | 125 | /// Location of the last node in the ring. 126 | osmium::Location last_location() const noexcept { 127 | assert(!m_way_node_list.empty()); 128 | return m_way_node_list.back().location(); 129 | } 130 | 131 | /// Return ID of this ring (defined as smallest ID of the ways making up the ring). 132 | osmium::object_id_type ring_id() const noexcept { 133 | return m_ring_id; 134 | } 135 | 136 | /** 137 | * Set ring ID. The ring will only get the new ID if it is smaller than the 138 | * old one. 139 | */ 140 | void update_ring_id(osmium::object_id_type new_id) noexcept { 141 | if (new_id < m_ring_id) { 142 | m_ring_id = new_id; 143 | } 144 | } 145 | 146 | /// Returns the number of ways making up this ring. 147 | unsigned int nways() const noexcept { 148 | return m_nways; 149 | } 150 | 151 | /// Returns the number of points in this ring. 152 | unsigned int npoints() const noexcept { 153 | return m_way_node_list.size(); 154 | } 155 | 156 | /// Returns true if the ring is closed. 157 | bool is_closed() const noexcept { 158 | return first_node_id() == last_node_id(); 159 | } 160 | 161 | /// Was this ring fixed because of missing/wrong OSM data? 162 | bool is_fixed() const noexcept { 163 | return m_fixed; 164 | } 165 | 166 | /** 167 | * When there are two different nodes with the same location 168 | * a situation can arise where a CoastlineRing looks not closed 169 | * when looking at the node IDs but looks closed then looking 170 | * at the location. To "fix" this we change the node ID of the 171 | * last node in the ring to be the same as the first. This 172 | * method does this. 173 | */ 174 | void fake_close() noexcept { 175 | assert(!m_way_node_list.empty()); 176 | m_way_node_list.back().set_ref(first_node_id()); 177 | } 178 | 179 | /** 180 | * Add pointers to the node locations to the given locmap. The 181 | * locmap can than later be used to directly put the locations 182 | * into the right place. 183 | */ 184 | void setup_locations(locmap_type& locmap); 185 | 186 | /** 187 | * Check whether all node locations for the ways are there. This 188 | * can happen if the input data is missing a node needed for a 189 | * way. The function returns the number of missing locations. 190 | */ 191 | unsigned int check_locations(bool output_missing); 192 | 193 | /// Add a new way to the front of this ring. 194 | void add_at_front(const osmium::Way& way); 195 | 196 | /// Add a new way to the end of this ring. 197 | void add_at_end(const osmium::Way& way); 198 | 199 | /** 200 | * Join the other ring to this one. The first node ID of the 201 | * other ring must be the same as the last node ID of this 202 | * ring. 203 | * The other ring can be destroyed afterwards. 204 | */ 205 | void join(const CoastlineRing& other); 206 | 207 | /** 208 | * Join the other ring to this one, possibly over a gap. 209 | * The other ring can be destroyed afterwards. 210 | */ 211 | void join_over_gap(const CoastlineRing& other); 212 | 213 | /** 214 | * Close ring by adding the first node to the end. If the 215 | * ring is already closed it is not changed. 216 | */ 217 | void close_ring(); 218 | 219 | /** 220 | * Close Antarctica ring by adding some nodes. 221 | */ 222 | void close_antarctica_ring(int epsg); 223 | 224 | /** 225 | * Create OGRPolygon for this ring. 226 | * 227 | * Caller takes ownership of the created object. 228 | * 229 | * @param geom_factory Geometry factory that should be used to build the geometry. 230 | * @param reverse Reverse the ring when creating the geometry. 231 | */ 232 | std::unique_ptr ogr_polygon(osmium::geom::OGRFactory<>& geom_factory, bool reverse) const; 233 | 234 | /** 235 | * Create OGRLineString for this ring. 236 | * 237 | * Caller takes ownership of the created object. 238 | * 239 | * @param geom_factory Geometry factory that should be used to build the geometry. 240 | * @param reverse Reverse the ring when creating the geometry. 241 | */ 242 | std::unique_ptr ogr_linestring(osmium::geom::OGRFactory<>& geom_factory, bool reverse) const; 243 | 244 | /** 245 | * Create OGRPoint for the first point in this ring. 246 | * 247 | * Caller takes ownership of created object. 248 | */ 249 | std::unique_ptr ogr_first_point() const; 250 | 251 | /** 252 | * Create OGRPoint for the last point in this ring. 253 | loca* 254 | * Caller takes ownership of created object. 255 | */ 256 | std::unique_ptr ogr_last_point() const; 257 | 258 | double distance_to_start_location(osmium::Location pos) const; 259 | 260 | void add_segments_to_vector(std::vector& segments) const; 261 | 262 | friend std::ostream& operator<<(std::ostream& out, const CoastlineRing& cp); 263 | 264 | }; // class CoastlineRing 265 | 266 | inline bool operator<(const CoastlineRing& lhs, const CoastlineRing& rhs) noexcept { 267 | return lhs.first_location() < rhs.first_location(); 268 | } 269 | 270 | #endif // COASTLINE_RING_HPP 271 | -------------------------------------------------------------------------------- /src/coastline_ring_collection.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COASTLINE_RING_COLLECTION_HPP 2 | #define COASTLINE_RING_COLLECTION_HPP 3 | 4 | /* 5 | 6 | Copyright 2012-2025 Jochen Topf . 7 | 8 | This file is part of OSMCoastline. 9 | 10 | OSMCoastline is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | OSMCoastline is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with OSMCoastline. If not, see . 22 | 23 | */ 24 | 25 | #include "coastline_ring.hpp" 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | class OGRGeometry; 38 | class OutputDatabase; 39 | class CoastlinePolygons; 40 | 41 | using coastline_rings_list_type = std::list>; 42 | using idmap_type = std::map; 43 | 44 | /** 45 | * A collection of CoastlineRing objects. Keeps a list of all start and end 46 | * nodes so it can efficiently join CoastlineRings. 47 | */ 48 | class CoastlineRingCollection { 49 | 50 | coastline_rings_list_type m_list; 51 | 52 | // Mapping from node IDs to CoastlineRings. 53 | idmap_type m_start_nodes; 54 | idmap_type m_end_nodes; 55 | 56 | unsigned int m_ways = 0; 57 | unsigned int m_rings_from_single_way = 0; 58 | unsigned int m_fixed_rings = 0; 59 | 60 | void add_partial_ring(const osmium::Way& way); 61 | 62 | osmium::geom::OGRFactory<> m_factory; 63 | 64 | public: 65 | 66 | using const_iterator = coastline_rings_list_type::const_iterator; 67 | 68 | CoastlineRingCollection() = default; 69 | 70 | /// Return the number of CoastlineRings in the collection. 71 | std::size_t size() const noexcept { 72 | return m_list.size(); 73 | } 74 | 75 | /** 76 | * Add way to collection. A new CoastlineRing will be created for the way 77 | * or it will be joined to an existing CoastlineRing. 78 | */ 79 | void add_way(const osmium::Way& way) { 80 | assert(!way.nodes().empty()); 81 | m_ways++; 82 | if (way.is_closed()) { 83 | m_rings_from_single_way++; 84 | m_list.push_back(std::make_shared(way)); 85 | } else { 86 | add_partial_ring(way); 87 | } 88 | } 89 | 90 | unsigned int num_ways() const noexcept { 91 | return m_ways; 92 | } 93 | 94 | unsigned int num_rings_from_single_way() const noexcept { 95 | return m_rings_from_single_way; 96 | } 97 | 98 | unsigned int num_unconnected_nodes() const noexcept { 99 | return m_start_nodes.size() + m_end_nodes.size(); 100 | } 101 | 102 | unsigned int num_fixed_rings() const noexcept { 103 | return m_fixed_rings; 104 | } 105 | 106 | void setup_locations(locmap_type& locmap); 107 | 108 | unsigned int check_locations(bool output_missing); 109 | 110 | std::vector add_polygons_to_vector(); 111 | 112 | unsigned int output_rings(OutputDatabase& output); 113 | 114 | unsigned int check_for_intersections(OutputDatabase& output, int segments_fd); 115 | 116 | bool close_antarctica_ring(int epsg); 117 | 118 | void close_rings(OutputDatabase& output, bool debug, double max_distance); 119 | 120 | unsigned int output_questionable(const CoastlinePolygons& polygons, OutputDatabase& output); 121 | 122 | private: 123 | 124 | struct Connection { 125 | 126 | double distance; 127 | osmium::object_id_type start_id; 128 | osmium::object_id_type end_id; 129 | 130 | Connection(double d, osmium::object_id_type s, osmium::object_id_type e) : 131 | distance(d), 132 | start_id(s), 133 | end_id(e) { 134 | } 135 | 136 | /** 137 | * Returns true if start or end ID of this connection is the same as the 138 | * other connections start or end ID. Used as predicate in std::remove_if(). 139 | */ 140 | bool operator()(const Connection& other) const noexcept { 141 | return start_id == other.start_id || end_id == other.end_id; 142 | } 143 | 144 | // Used in std::sort 145 | static bool sort_by_distance(const Connection& a, const Connection& b) noexcept { 146 | return a.distance > b.distance; 147 | } 148 | 149 | }; // struct Connection 150 | 151 | }; // class CoastlineRingCollection 152 | 153 | #endif // COASTLINE_RING_COLLECTION_HPP 154 | -------------------------------------------------------------------------------- /src/nodegrid2opl.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2012-2025 Jochen Topf . 4 | 5 | This file is part of OSMCoastline. 6 | 7 | OSMCoastline is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | OSMCoastline is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with OSMCoastline. If not, see . 19 | 20 | */ 21 | 22 | /** 23 | * This is a program used for testing only. It interprets an "ASCII art" 24 | * visualization of nodes in a grid read from STDIN and creates an OPL 25 | * output on STDOUT with those nodes. 26 | * 27 | * Nodes are represented by the characters 0 to 9 and a to z. All other 28 | * characters are ignored and are only used to form the grid. Here is 29 | * an example: 30 | * 31 | * ------------------ 32 | * 33 | * 0 3 1 34 | * \ 35 | * 2--a-5 36 | * 37 | * ------------------ 38 | * 39 | * This will result in something like: 40 | * 41 | * n100 v1 x1.030000 y1.990000 42 | * n101 v1 x1.120000 y1.990000 43 | * n102 v1 x1.050000 y1.970000 44 | * n103 v1 x1.070000 y1.990000 45 | * n105 v1 x1.100000 y1.970000 46 | * n110 v1 x1.080000 y1.970000 47 | * 48 | */ 49 | 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | 57 | const int id_offset = 100; 58 | 59 | namespace { 60 | 61 | bool add_node(std::vector& nodes, char c, double x, double y) { 62 | static std::set ids; 63 | int id = id_offset; 64 | 65 | if (c >= '0' && c <= '9') { 66 | id += c - '0'; 67 | } else { 68 | id += c - 'a' + 10; 69 | } 70 | 71 | if (ids.count(id)) { 72 | std::cerr << "ID seen twice: " << c << " (" << id << ")\n"; 73 | return false; 74 | } 75 | 76 | ids.insert(id); 77 | nodes.push_back("n" + std::to_string(id) + " v1 x" + std::to_string(x) + " y" + std::to_string(y) + "\n"); 78 | return true; 79 | } 80 | 81 | } // anonymous namespace 82 | 83 | int main() { 84 | const double scale = 0.01; 85 | const double offset = 1; 86 | 87 | std::vector nodes; 88 | 89 | int x = 1; 90 | int y = 100; 91 | 92 | for (std::string line; std::getline(std::cin, line);) { 93 | for (const auto c : line) { 94 | if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')) { 95 | if (!add_node(nodes, c, offset + (x * scale), offset + (y * scale))) { 96 | return 1; 97 | } 98 | } 99 | ++x; 100 | } 101 | x = 1; 102 | --y; 103 | } 104 | 105 | std::sort(nodes.begin(), nodes.end()); 106 | 107 | const auto it = std::ostream_iterator(std::cout); 108 | std::copy(nodes.begin(), nodes.end(), it); 109 | 110 | return 0; 111 | } 112 | 113 | -------------------------------------------------------------------------------- /src/options.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2012-2025 Jochen Topf . 4 | 5 | This file is part of OSMCoastline. 6 | 7 | OSMCoastline is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | OSMCoastline is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with OSMCoastline. If not, see . 19 | 20 | */ 21 | 22 | #include "return_codes.hpp" 23 | #include "options.hpp" 24 | #include "version.hpp" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #ifdef _MSC_VER 33 | #define strcasecmp _stricmp 34 | #endif 35 | 36 | #include 37 | 38 | namespace { 39 | 40 | void print_help() { 41 | std::cout << "Usage: osmcoastline [OPTIONS] OSMFILE\n" 42 | << "\nOptions:\n" 43 | << " -h, --help - This help message\n" 44 | << " -c, --close-distance=DIST - Distance between nodes under which open rings\n" 45 | << " are closed (0 - disable closing of rings)\n" 46 | << " -b, --bbox-overlap=OVERLAP - Set overlap when splitting polygons\n" 47 | << " -i, --no-index - Do not create spatial indexes in output db\n" 48 | << " -d, --debug - Enable debugging output\n" 49 | << " -e, --exit-ignore-warnings - Exit with code 0 even if there are warnings\n" 50 | << " -f, --overwrite - Overwrite output file if it already exists\n" 51 | << " -g, --gdal-driver=DRIVER - GDAL driver (SQLite or ESRI Shapefile)\n" 52 | << " -l, --output-lines - Output coastlines as lines to database file\n" 53 | << " -m, --max-points=NUM - Split lines/polygons with more than this many\n" 54 | << " points (0 - disable splitting)\n" 55 | << " -o, --output-database=FILE - Database file for output\n" 56 | << " -p, --output-polygons=land|water|both|none\n" 57 | << " - Which polygons to write out (default: land)\n" 58 | << " -r, --output-rings - Output rings to database file\n" 59 | << " -s, --srs=EPSGCODE - Set SRS (4326 for WGS84 (default) or 3857)\n" 60 | << " -S, --write-segments=FILE - Write segments to given file\n" 61 | << " -v, --verbose - Verbose output\n" 62 | << " -V, --version - Show version and exit\n" 63 | << "\n"; 64 | } 65 | 66 | void print_version() { 67 | std::cout << "osmcoastline " << get_osmcoastline_long_version() << "\n" 68 | << get_libosmium_version() << '\n' 69 | << "Supported PBF compression types:"; 70 | for (const auto& type : osmium::io::supported_pbf_compression_types()) { 71 | std::cout << " " << type; 72 | } 73 | std::cout << "\n\nCopyright (C) 2012-2025 Jochen Topf \n" 74 | << "License: GNU GENERAL PUBLIC LICENSE Version 3 .\n" 75 | << "This is free software: you are free to change and redistribute it.\n" 76 | << "There is NO WARRANTY, to the extent permitted by law.\n"; 77 | } 78 | 79 | /** 80 | * Get EPSG code from text. This method knows about a few common cases 81 | * of specifying WGS84 or the "Web Mercator" SRS. More are currently 82 | * not supported. 83 | */ 84 | int get_epsg(const char* text) { 85 | if (!strcasecmp(text, "WGS84") || !std::strcmp(text, "4326")) { 86 | return 4326; 87 | } 88 | if (!std::strcmp(text, "3857")) { 89 | return 3857; 90 | } 91 | if (!std::strcmp(text, "3785") || !std::strcmp(text, "900913")) { 92 | std::cerr << "Please use code 3857 for the 'Web Mercator' projection!\n"; 93 | std::exit(return_code_cmdline); 94 | } 95 | std::cerr << "Unknown SRS '" << text << "'. Currently only 4326 (WGS84) and 3857 ('Web Mercator') are supported.\n"; 96 | std::exit(return_code_cmdline); 97 | } 98 | 99 | } // anonymous namespace 100 | 101 | int Options::parse(int argc, char* argv[]) { 102 | static struct option long_options[] = { 103 | {"bbox-overlap", required_argument, nullptr, 'b'}, 104 | {"close-distance", required_argument, nullptr, 'c'}, 105 | {"no-index", no_argument, nullptr, 'i'}, 106 | {"debug", no_argument, nullptr, 'd'}, 107 | {"exit-ignore-warnings", no_argument, nullptr, 'e'}, 108 | {"gdal-driver", required_argument, nullptr, 'g'}, 109 | {"help", no_argument, nullptr, 'h'}, 110 | {"output-lines", no_argument, nullptr, 'l'}, 111 | {"max-points", required_argument, nullptr, 'm'}, 112 | {"output-database", required_argument, nullptr, 'o'}, 113 | {"output-polygons", required_argument, nullptr, 'p'}, 114 | {"output-rings", no_argument, nullptr, 'r'}, 115 | {"overwrite", no_argument, nullptr, 'f'}, 116 | {"srs", required_argument, nullptr, 's'}, 117 | {"write-segments", required_argument, nullptr, 'S'}, 118 | {"verbose", no_argument, nullptr, 'v'}, 119 | {"version", no_argument, nullptr, 'V'}, 120 | {nullptr, 0, nullptr, 0} 121 | }; 122 | 123 | while (true) { 124 | const int c = getopt_long(argc, argv, "b:c:ideg:hlm:o:p:rfs:S:vV", long_options, nullptr); 125 | if (c == -1) { 126 | break; 127 | } 128 | 129 | switch (c) { 130 | case 'b': 131 | bbox_overlap = std::atof(optarg); // NOLINT(cert-err34-c) atof is good enough for this use case 132 | break; 133 | case 'c': 134 | close_distance = std::atoi(optarg); // NOLINT(cert-err34-c) atoi is good enough for this use case 135 | break; 136 | case 'i': 137 | create_index = false; 138 | break; 139 | case 'd': 140 | debug = true; 141 | std::cerr << "Enabled debug option\n"; 142 | break; 143 | case 'e': 144 | exit_ignore_warnings = true; 145 | break; 146 | case 'h': 147 | print_help(); 148 | return return_code_ok; 149 | case 'g': 150 | driver = optarg; 151 | break; 152 | case 'l': 153 | output_lines = true; 154 | break; 155 | case 'm': 156 | max_points_in_polygon = std::atoi(optarg); // NOLINT(cert-err34-c) atoi is good enough for this use case 157 | if (max_points_in_polygon == 0) { 158 | split_large_polygons = false; 159 | } 160 | break; 161 | case 'p': 162 | if (!std::strcmp(optarg, "none")) { 163 | output_polygons = output_polygon_type::none; 164 | } else if (!std::strcmp(optarg, "land")) { 165 | output_polygons = output_polygon_type::land; 166 | } else if (!std::strcmp(optarg, "water")) { 167 | output_polygons = output_polygon_type::water; 168 | } else if (!std::strcmp(optarg, "both")) { 169 | output_polygons = output_polygon_type::both; 170 | } else { 171 | std::cerr << "Unknown argument '" << optarg << "' for -p/--output-polygon option\n"; 172 | return return_code_cmdline; 173 | } 174 | break; 175 | case 'o': 176 | output_database = optarg; 177 | break; 178 | case 'r': 179 | output_rings = true; 180 | break; 181 | case 'f': 182 | overwrite_output = true; 183 | break; 184 | case 's': 185 | epsg = get_epsg(optarg); 186 | break; 187 | case 'S': 188 | segmentfile = optarg; 189 | break; 190 | case 'v': 191 | verbose = true; 192 | break; 193 | case 'V': 194 | print_version(); 195 | return return_code_ok; 196 | default: 197 | return return_code_cmdline; 198 | } 199 | } 200 | 201 | if (!split_large_polygons && (output_polygons == output_polygon_type::water || output_polygons == output_polygon_type::both)) { 202 | std::cerr << "Can not use -m/--max-points=0 when writing out water polygons\n"; 203 | return return_code_cmdline; 204 | } 205 | 206 | if (optind != argc - 1) { 207 | std::cerr << "Usage: osmcoastline [OPTIONS] OSMFILE\n"; 208 | return return_code_cmdline; 209 | } 210 | 211 | if (output_database.empty()) { 212 | std::cerr << "Missing --output-database/-o option.\n"; 213 | return return_code_cmdline; 214 | } 215 | 216 | if (bbox_overlap == -1) { 217 | if (epsg == 4326) { 218 | bbox_overlap = 0.0001; 219 | } else { 220 | bbox_overlap = 10; 221 | } 222 | } 223 | 224 | inputfile = argv[optind]; 225 | 226 | return -1; 227 | } 228 | 229 | -------------------------------------------------------------------------------- /src/options.hpp: -------------------------------------------------------------------------------- 1 | #ifndef OPTIONS_HPP 2 | #define OPTIONS_HPP 3 | 4 | /* 5 | 6 | Copyright 2012-2025 Jochen Topf . 7 | 8 | This file is part of OSMCoastline. 9 | 10 | OSMCoastline is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | OSMCoastline is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with OSMCoastline. If not, see . 22 | 23 | */ 24 | 25 | #include 26 | 27 | enum class output_polygon_type { 28 | none = 0, 29 | land = 1, 30 | water = 2, 31 | both = 3 32 | }; 33 | 34 | /** 35 | * This class encapsulates the command line parsing. 36 | */ 37 | struct Options { 38 | 39 | /// Input OSM file name. 40 | std::string inputfile; 41 | 42 | /// Overlap when splitting polygons. 43 | double bbox_overlap = -1.0; 44 | 45 | /** 46 | * If the distance between two ring-endnodes is smaller than this the ring 47 | * can be closed there. 48 | */ 49 | double close_distance = 1.0; 50 | 51 | /// Add spatial index to Spatialite database tables? 52 | bool create_index = true; 53 | 54 | /// Show debug output? 55 | bool debug = false; 56 | 57 | /// GDAL driver. 58 | std::string driver = "SQLite"; 59 | 60 | /// Maximum number of points in polygons. 61 | int max_points_in_polygon = 1000; 62 | 63 | /// Should large polygons be split? 64 | bool split_large_polygons = true; 65 | 66 | /// What polygons should be written out? 67 | output_polygon_type output_polygons = output_polygon_type::land; 68 | 69 | /// Output database file name. 70 | std::string output_database; 71 | 72 | /// Should output database be overwritten 73 | bool overwrite_output = false; 74 | 75 | /// Should the rings output table be populated? 76 | bool output_rings = false; 77 | 78 | /// Should the lines output table be populated? 79 | bool output_lines = false; 80 | 81 | /// EPSG code of output SRS. 82 | int epsg = 4326; 83 | 84 | /// Should the coastline be simplified? 85 | bool simplify = false; 86 | 87 | /// Tolerance for simplification 88 | double tolerance = 0.0; 89 | 90 | /// Verbose output? 91 | bool verbose = false; 92 | 93 | /// Exit program with code 0 even if there are warnings? 94 | bool exit_ignore_warnings = false; 95 | 96 | /// Name of optional segment file 97 | std::string segmentfile; 98 | 99 | int parse(int argc, char* argv[]); 100 | 101 | }; // struct Options 102 | 103 | #endif // OPTIONS_HPP 104 | -------------------------------------------------------------------------------- /src/osmcoastline_filter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2012-2025 Jochen Topf . 4 | 5 | This file is part of OSMCoastline. 6 | 7 | OSMCoastline is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | OSMCoastline is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with OSMCoastline. If not, see . 19 | 20 | */ 21 | 22 | #include "return_codes.hpp" 23 | #include "version.hpp" 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | namespace { 50 | 51 | void print_help() { 52 | std::cout << "Usage: osmcoastline_filter [OPTIONS] OSMFILE\n" 53 | << "\nOptions:\n" 54 | << " -f, --format - Set output format (default: pbf)\n" 55 | << " -h, --help - This help message\n" 56 | << " -o, --output=OSMFILE - Where to write output (default: none)\n" 57 | << " -v, --verbose - Verbose output\n" 58 | << " -V, --version - Show version and exit\n" 59 | << "\n"; 60 | } 61 | 62 | } // anonymous namespace 63 | 64 | int main(int argc, char* argv[]) { 65 | std::string output_filename; 66 | std::string output_file_format{"pbf"}; 67 | bool verbose = false; 68 | 69 | static struct option long_options[] = { 70 | {"format", required_argument, nullptr, 'f'}, 71 | {"help", no_argument, nullptr, 'h'}, 72 | {"output", required_argument, nullptr, 'o'}, 73 | {"verbose", no_argument, nullptr, 'v'}, 74 | {"version", no_argument, nullptr, 'V'}, 75 | {nullptr, 0, nullptr, 0} 76 | }; 77 | 78 | while (true) { 79 | const int c = getopt_long(argc, argv, "f:ho:vV", long_options, nullptr); 80 | if (c == -1) { 81 | break; 82 | } 83 | 84 | switch (c) { 85 | case 'f': 86 | output_file_format = optarg; 87 | break; 88 | case 'h': 89 | print_help(); 90 | return return_code_ok; 91 | case 'o': 92 | output_filename = optarg; 93 | break; 94 | case 'v': 95 | verbose = true; 96 | break; 97 | case 'V': 98 | std::cout << "osmcoastline_filter " << get_osmcoastline_long_version() << " / " << get_libosmium_version() << '\n' 99 | << "Copyright (C) 2012-2025 Jochen Topf \n" 100 | << "License: GNU GENERAL PUBLIC LICENSE Version 3 .\n" 101 | << "This is free software: you are free to change and redistribute it.\n" 102 | << "There is NO WARRANTY, to the extent permitted by law.\n"; 103 | return return_code_ok; 104 | default: 105 | return return_code_fatal; 106 | } 107 | } 108 | 109 | if (output_filename.empty()) { 110 | std::cerr << "Missing -o/--output=OSMFILE option\n"; 111 | return return_code_cmdline; 112 | } 113 | 114 | if (optind != argc - 1) { 115 | std::cerr << "Usage: osmcoastline_filter [OPTIONS] OSMFILE\n"; 116 | return return_code_cmdline; 117 | } 118 | 119 | try { 120 | // The vout object is an output stream we can write to instead of 121 | // std::cerr. Nothing is written if we are not in verbose mode. 122 | // The running time will be prepended to output lines. 123 | osmium::util::VerboseOutput vout{verbose}; 124 | 125 | osmium::io::Header header; 126 | header.set("generator", std::string{"osmcoastline_filter/"} + get_osmcoastline_version()); 127 | header.add_box(osmium::Box{-180.0, -90.0, 180.0, 90.0}); 128 | 129 | const osmium::io::File infile{argv[optind]}; 130 | 131 | vout << "Started osmcoastline_filter " << get_osmcoastline_long_version() << " / " << get_libosmium_version() << '\n'; 132 | 133 | const osmium::io::File output_file{output_filename, output_file_format}; 134 | osmium::io::Writer writer{output_file, header}; 135 | auto output_it = osmium::io::make_output_iterator(writer); 136 | 137 | osmium::index::IdSetSmall ids; 138 | 139 | vout << "Reading ways (1st pass through input file)...\n"; 140 | { 141 | osmium::io::Reader reader{infile, osmium::osm_entity_bits::way}; 142 | const auto ways = osmium::io::make_input_iterator_range(reader); 143 | for (const osmium::Way& way : ways) { 144 | if (way.tags().has_tag("natural", "coastline")) { 145 | *output_it++ = way; 146 | for (const auto& nr : way.nodes()) { 147 | ids.set(nr.ref()); 148 | } 149 | } 150 | } 151 | reader.close(); 152 | } 153 | 154 | vout << "Preparing node ID list...\n"; 155 | ids.sort_unique(); 156 | 157 | vout << "Reading nodes (2nd pass through input file)...\n"; 158 | { 159 | osmium::io::Reader reader{infile, osmium::osm_entity_bits::node}; 160 | const auto nodes = osmium::io::make_input_iterator_range(reader); 161 | 162 | auto first = ids.cbegin(); 163 | const auto last = ids.cend(); 164 | std::copy_if(nodes.cbegin(), nodes.cend(), output_it, [&first, &last](const osmium::Node& node){ 165 | while (*first < node.id() && first != last) { 166 | ++first; 167 | } 168 | 169 | if (node.id() == *first) { 170 | if (first != last) { 171 | ++first; 172 | } 173 | return true; 174 | } 175 | 176 | return node.tags().has_tag("natural", "coastline"); 177 | }); 178 | 179 | reader.close(); 180 | } 181 | 182 | writer.close(); 183 | 184 | vout << "All done.\n"; 185 | const osmium::MemoryUsage mem; 186 | if (mem.current() > 0) { 187 | vout << "Memory used: current: " << mem.current() << " MBytes\n" 188 | << " peak: " << mem.peak() << " MBytes\n"; 189 | } 190 | } catch (const std::exception& e) { 191 | std::cerr << e.what() << '\n'; 192 | return return_code_fatal; 193 | } 194 | 195 | return return_code_ok; 196 | } 197 | 198 | -------------------------------------------------------------------------------- /src/osmcoastline_segments.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2012-2025 Jochen Topf . 4 | 5 | This file is part of OSMCoastline. 6 | 7 | OSMCoastline is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | OSMCoastline is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with OSMCoastline. If not, see . 19 | 20 | */ 21 | 22 | #include "return_codes.hpp" 23 | #include "version.hpp" 24 | 25 | #include 26 | #include 27 | 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | #ifndef _MSC_VER 47 | # include 48 | #else 49 | # include 50 | #endif 51 | 52 | using segvec = std::vector; 53 | 54 | class InputFile { 55 | 56 | std::string m_filename; 57 | int m_fd; 58 | 59 | public: 60 | 61 | explicit InputFile(const std::string& filename) : 62 | m_filename(filename), 63 | m_fd(::open(filename.c_str(), O_RDONLY)) { 64 | if (m_fd == -1) { 65 | throw std::system_error{errno, std::system_category(), std::string{"Opening '"} + filename + "' failed"}; 66 | } 67 | } 68 | 69 | int fd() const noexcept { 70 | return m_fd; 71 | } 72 | 73 | std::size_t size() const { 74 | struct stat s; // NOLINT(cppcoreguidelines-pro-type-member-init, hicpp-member-init) 75 | if (::fstat(m_fd, &s) != 0) { 76 | throw std::system_error{errno, std::system_category(), std::string{"Can't get file size for '"} + m_filename + "'"}; 77 | } 78 | return static_cast(s.st_size); 79 | } 80 | 81 | }; // class InputFile 82 | 83 | namespace { 84 | 85 | void print_help() { 86 | } 87 | 88 | void add_segment(gdalcpp::Layer& layer, int change, const osmium::UndirectedSegment& segment) { 89 | auto linestring = std::make_unique(); 90 | linestring->addPoint(segment.first().lon(), segment.first().lat()); 91 | linestring->addPoint(segment.second().lon(), segment.second().lat()); 92 | 93 | gdalcpp::Feature feature(layer, std::move(linestring)); 94 | feature.set_field("change", change); 95 | feature.add_to_layer(); 96 | } 97 | 98 | void output_ogr(const std::string& filename, const std::string& driver_name, const segvec& removed_segments, const segvec& added_segments) { 99 | gdalcpp::Dataset dataset{driver_name, filename}; 100 | 101 | gdalcpp::Layer layer{dataset, "changes", wkbLineString}; 102 | layer.add_field("change", OFTInteger, 1); 103 | layer.start_transaction(); 104 | 105 | for (const auto& segment : removed_segments) { 106 | add_segment(layer, 0, segment); 107 | } 108 | 109 | for (const auto& segment : added_segments) { 110 | add_segment(layer, 1, segment); 111 | } 112 | 113 | layer.commit_transaction(); 114 | } 115 | 116 | } // anonymous namespace 117 | 118 | int main(int argc, char *argv[]) { 119 | bool dump = false; 120 | std::string format = "ESRI Shapefile"; 121 | std::string geom; 122 | 123 | static struct option long_options[] = { 124 | {"dump", no_argument, nullptr, 'd'}, 125 | {"format", required_argument, nullptr, 'f'}, 126 | {"geom", required_argument, nullptr, 'g'}, 127 | {"help", no_argument, nullptr, 'h'}, 128 | {"version", no_argument, nullptr, 'V'}, 129 | {nullptr, 0, nullptr, 0} 130 | }; 131 | 132 | while (true) { 133 | const int c = getopt_long(argc, argv, "df:g:hV", long_options, nullptr); 134 | if (c == -1) { 135 | break; 136 | } 137 | 138 | switch (c) { 139 | case 'd': 140 | dump = true; 141 | break; 142 | case 'f': 143 | format = optarg; 144 | break; 145 | case 'g': 146 | geom = optarg; 147 | break; 148 | case 'h': { 149 | std::cout << "Usage: osmcoastline_segments [OPTIONS] SEGFILE1 SEGFILE2\n"; 150 | print_help(); 151 | return return_code_ok; 152 | } 153 | case 'V': 154 | std::cout << "osmcoastline_segments " << get_osmcoastline_long_version() << " / " << get_libosmium_version() << '\n' 155 | << "Copyright (C) 2012-2025 Jochen Topf \n" 156 | << "License: GNU GENERAL PUBLIC LICENSE Version 3 .\n" 157 | << "This is free software: you are free to change and redistribute it.\n" 158 | << "There is NO WARRANTY, to the extent permitted by law.\n"; 159 | return return_code_ok; 160 | default: 161 | break; 162 | } 163 | } 164 | 165 | if (optind != argc - 2) { 166 | std::cerr << "Usage: " << argv[0] << " [OPTIONS] SEGFILE1 SEGFILE2\n"; 167 | return return_code_cmdline; 168 | } 169 | 170 | try { 171 | segvec removed_segments; 172 | segvec added_segments; 173 | 174 | const InputFile file1{argv[optind]}; 175 | const InputFile file2{argv[optind + 1]}; 176 | 177 | const osmium::util::TypedMemoryMapping m1{file1.size() / sizeof(osmium::UndirectedSegment), osmium::util::MemoryMapping::mapping_mode::readonly, file1.fd()}; 178 | const osmium::util::TypedMemoryMapping m2{file2.size() / sizeof(osmium::UndirectedSegment), osmium::util::MemoryMapping::mapping_mode::readonly, file2.fd()}; 179 | 180 | std::set_difference(m1.cbegin(), m1.cend(), m2.cbegin(), m2.cend(), std::back_inserter(removed_segments)); 181 | std::set_difference(m2.cbegin(), m2.cend(), m1.cbegin(), m1.cend(), std::back_inserter(added_segments)); 182 | 183 | if (dump) { 184 | std::cout << "Removed:\n"; 185 | for (const auto& segment : removed_segments) { 186 | std::cout << " " << segment << "\n"; 187 | } 188 | 189 | std::cout << "Added:\n"; 190 | for (const auto& segment : added_segments) { 191 | std::cout << " " << segment << "\n"; 192 | } 193 | } else if (!geom.empty()) { 194 | output_ogr(geom, format, removed_segments, added_segments); 195 | } 196 | 197 | return (removed_segments.empty() && added_segments.empty()) ? 0 : 1; 198 | } catch (const std::exception& e) { 199 | std::cerr << e.what() << '\n'; 200 | return return_code_fatal; 201 | } 202 | 203 | return return_code_ok; 204 | } 205 | 206 | -------------------------------------------------------------------------------- /src/osmcoastline_ways.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2012-2025 Jochen Topf . 4 | 5 | This file is part of OSMCoastline. 6 | 7 | OSMCoastline is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | OSMCoastline is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with OSMCoastline. If not, see . 19 | 20 | */ 21 | 22 | #include "return_codes.hpp" 23 | #include "version.hpp" 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include // IWYU pragma: keep 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include 39 | 40 | #include 41 | #include 42 | 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | 51 | using index_type = osmium::index::map::SparseMemArray; 52 | using location_handler_type = osmium::handler::NodeLocationsForWays; 53 | 54 | class CoastlineWaysHandler : public osmium::handler::Handler { 55 | 56 | double m_length = 0.0; 57 | 58 | gdalcpp::Dataset m_dataset; 59 | gdalcpp::Layer m_layer_ways; 60 | 61 | osmium::geom::OGRFactory<> m_factory; 62 | 63 | public: 64 | 65 | explicit CoastlineWaysHandler(const std::string& db_filename) : 66 | m_dataset("SQLite", db_filename, gdalcpp::SRS{}, {"SPATIALITE=TRUE", "INIT_WITH_EPSG=no" }), 67 | m_layer_ways(m_dataset, "ways", wkbLineString) { 68 | 69 | m_layer_ways.add_field("way_id", OFTString, 10); 70 | m_layer_ways.add_field("name", OFTString, 100); 71 | m_layer_ways.add_field("source", OFTString, 255); 72 | m_layer_ways.add_field("bogus", OFTString, 1); 73 | m_layer_ways.start_transaction(); 74 | } 75 | 76 | CoastlineWaysHandler(const CoastlineWaysHandler&) = delete; 77 | CoastlineWaysHandler operator=(const CoastlineWaysHandler&) = delete; 78 | 79 | CoastlineWaysHandler(CoastlineWaysHandler&&) = delete; 80 | CoastlineWaysHandler operator=(CoastlineWaysHandler&&) = delete; 81 | 82 | ~CoastlineWaysHandler() { 83 | m_layer_ways.commit_transaction(); 84 | } 85 | 86 | void way(const osmium::Way& way) { 87 | m_length += osmium::geom::haversine::distance(way.nodes()); 88 | try { 89 | std::unique_ptr ogrlinestring = m_factory.create_linestring(way); 90 | gdalcpp::Feature feature(m_layer_ways, std::move(ogrlinestring)); 91 | feature.set_field("way_id", std::to_string(way.id()).c_str()); 92 | feature.set_field("name", way.tags().get_value_by_key("name")); 93 | feature.set_field("source", way.tags().get_value_by_key("source")); 94 | 95 | const bool bogus = way.tags().has_tag("coastline", "bogus"); 96 | feature.set_field("bogus", bogus ? "t" : "f"); 97 | feature.add_to_layer(); 98 | } catch (const osmium::geometry_error&) { 99 | std::cerr << "Ignoring illegal geometry for way " << way.id() << ".\n"; 100 | } 101 | } 102 | 103 | double sum_length() const noexcept { 104 | return m_length; 105 | } 106 | 107 | }; // class CoastlineWaysHandler 108 | 109 | int main(int argc, char* argv[]) { 110 | if (argc >= 2) { 111 | if (!std::strcmp(argv[1], "--help") || !std::strcmp(argv[1], "-h")) { 112 | std::cout << "Usage: osmcoastline_ways OSMFILE [WAYSDB]\n"; 113 | return return_code_ok; 114 | } 115 | 116 | if (!std::strcmp(argv[1], "--version") || !std::strcmp(argv[1], "-V")) { 117 | std::cout << "osmcoastline_ways " << get_osmcoastline_long_version() << " / " << get_libosmium_version() << '\n' 118 | << "Copyright (C) 2012-2025 Jochen Topf \n" 119 | << "License: GNU GENERAL PUBLIC LICENSE Version 3 .\n" 120 | << "This is free software: you are free to change and redistribute it.\n" 121 | << "There is NO WARRANTY, to the extent permitted by law.\n"; 122 | return return_code_ok; 123 | } 124 | } 125 | 126 | if (argc != 2 && argc != 3) { 127 | std::cerr << "Usage: osmcoastline_ways OSMFILE [WAYSDB]\n"; 128 | return return_code_cmdline; 129 | } 130 | 131 | CPLSetConfigOption("OGR_SQLITE_SYNCHRONOUS", "OFF"); 132 | 133 | try { 134 | const std::string input_osm_filename{argv[1]}; 135 | 136 | std::string output_db_filename{"coastline-ways.db"}; 137 | if (argc >= 3) { 138 | output_db_filename = argv[2]; 139 | } 140 | 141 | index_type index_pos; 142 | index_type index_neg; 143 | location_handler_type location_handler{index_pos, index_neg}; 144 | 145 | const osmium::io::File infile{input_osm_filename}; 146 | osmium::io::Reader reader1{infile, osmium::osm_entity_bits::node}; 147 | osmium::apply(reader1, location_handler); 148 | reader1.close(); 149 | 150 | CoastlineWaysHandler coastline_ways_handler{output_db_filename}; 151 | osmium::io::Reader reader2{infile, osmium::osm_entity_bits::way}; 152 | osmium::apply(reader2, location_handler, coastline_ways_handler); 153 | reader2.close(); 154 | 155 | std::cerr << "Sum of way lengths: " << std::fixed << (coastline_ways_handler.sum_length() / 1000) << "km\n"; 156 | } catch (const std::exception& e) { 157 | std::cerr << e.what() << '\n'; 158 | return return_code_fatal; 159 | } 160 | 161 | return return_code_ok; 162 | } 163 | 164 | -------------------------------------------------------------------------------- /src/output_database.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2012-2025 Jochen Topf . 4 | 5 | This file is part of OSMCoastline. 6 | 7 | OSMCoastline is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | OSMCoastline is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with OSMCoastline. If not, see . 19 | 20 | */ 21 | 22 | #include "options.hpp" 23 | #include "output_database.hpp" 24 | #include "srs.hpp" 25 | #include "stats.hpp" 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | OutputDatabase::OutputDatabase(const std::string& driver, const std::string& outdb, SRS& srs, bool with_index) : 42 | m_driver(driver), 43 | m_with_index(with_index), 44 | m_srs(srs), 45 | m_dataset(driver, outdb, gdalcpp::SRS(*srs.out()), driver_options()), 46 | m_layer_error_points(m_dataset, "error_points", wkbPoint, layer_options()), 47 | m_layer_error_lines(m_dataset, "error_lines", wkbLineString, layer_options()), 48 | m_layer_rings(m_dataset, "rings", wkbPolygon, layer_options()), 49 | m_layer_land_polygons(m_dataset, "land_polygons", wkbPolygon, layer_options()), 50 | m_layer_water_polygons(m_dataset, "water_polygons", wkbPolygon, layer_options()), 51 | m_layer_lines(m_dataset, "lines", wkbLineString, layer_options()) { 52 | 53 | m_layer_error_points.add_field("osm_id", OFTString, 10); 54 | m_layer_error_points.add_field("error", OFTString, 16); 55 | 56 | m_layer_error_lines.add_field("osm_id", OFTString, 10); 57 | m_layer_error_lines.add_field("error", OFTString, 16); 58 | 59 | m_layer_rings.add_field("osm_id", OFTString, 10); 60 | m_layer_rings.add_field("nways", OFTInteger, 6); 61 | m_layer_rings.add_field("npoints", OFTInteger, 8); 62 | m_layer_rings.add_field("fixed", OFTInteger, 1); 63 | m_layer_rings.add_field("land", OFTInteger, 1); 64 | m_layer_rings.add_field("valid", OFTInteger, 1); 65 | 66 | if (m_driver == "SQLite") { 67 | m_dataset.exec("CREATE TABLE options (overlap REAL, close_distance REAL, max_points_in_polygons INTEGER, split_large_polygons INTEGER)"); 68 | m_dataset.exec("CREATE TABLE meta (" 69 | "timestamp TEXT, " 70 | "runtime INTEGER, " 71 | "memory_usage INTEGER, " 72 | "num_ways INTEGER, " 73 | "num_unconnected_nodes INTEGER, " 74 | "num_rings INTEGER, " 75 | "num_rings_from_single_way INTEGER, " 76 | "num_rings_fixed INTEGER, " 77 | "num_rings_turned_around INTEGER, " 78 | "num_land_polygons_before_split INTEGER, " 79 | "num_land_polygons_after_split INTEGER)"); 80 | } 81 | 82 | m_dataset.start_transaction(); 83 | m_layer_rings.start_transaction(); 84 | m_layer_land_polygons.start_transaction(); 85 | m_layer_water_polygons.start_transaction(); 86 | m_layer_lines.start_transaction(); 87 | m_layer_error_points.start_transaction(); 88 | m_layer_error_lines.start_transaction(); 89 | } 90 | 91 | void OutputDatabase::set_options(const Options& options) { 92 | if (m_driver != "SQLite") { 93 | return; 94 | } 95 | 96 | std::ostringstream sql; 97 | 98 | sql << "INSERT INTO options (overlap, close_distance, max_points_in_polygons, split_large_polygons) VALUES (" 99 | << options.bbox_overlap << ", "; 100 | 101 | if (options.close_distance == 0) { 102 | sql << "NULL, "; 103 | } else { 104 | sql << options.close_distance << ", "; 105 | } 106 | 107 | sql << options.max_points_in_polygon << ", " 108 | << (options.split_large_polygons ? 1 : 0) 109 | << ")"; 110 | 111 | m_dataset.exec(sql.str()); 112 | } 113 | 114 | void OutputDatabase::set_meta(std::time_t runtime, int memory_usage, const Stats& stats) { 115 | if (m_driver != "SQLite") { 116 | return; 117 | } 118 | 119 | std::ostringstream sql; 120 | 121 | sql << "INSERT INTO meta (timestamp, runtime, memory_usage, " 122 | << "num_ways, num_unconnected_nodes, num_rings, num_rings_from_single_way, num_rings_fixed, num_rings_turned_around, " 123 | << "num_land_polygons_before_split, num_land_polygons_after_split) VALUES (datetime('now'), " 124 | << runtime << ", " 125 | << memory_usage << ", " 126 | << stats.ways << ", " 127 | << stats.unconnected_nodes << ", " 128 | << stats.rings << ", " 129 | << stats.rings_from_single_way << ", " 130 | << stats.rings_fixed << ", " 131 | << stats.rings_turned_around << ", " 132 | << stats.land_polygons_before_split << ", " 133 | << stats.land_polygons_after_split 134 | << ")"; 135 | 136 | m_dataset.exec(sql.str()); 137 | } 138 | 139 | void OutputDatabase::commit() { 140 | m_layer_error_lines.commit_transaction(); 141 | m_layer_error_points.commit_transaction(); 142 | m_layer_lines.commit_transaction(); 143 | m_layer_water_polygons.commit_transaction(); 144 | m_layer_land_polygons.commit_transaction(); 145 | m_layer_rings.commit_transaction(); 146 | m_dataset.commit_transaction(); 147 | } 148 | 149 | void OutputDatabase::add_error_point(std::unique_ptr&& point, const char* error, osmium::object_id_type id) { 150 | m_srs.transform(point.get()); 151 | gdalcpp::Feature feature{m_layer_error_points, std::move(point)}; 152 | feature.set_field("osm_id", std::to_string(id).c_str()); 153 | feature.set_field("error", error); 154 | feature.add_to_layer(); 155 | } 156 | 157 | void OutputDatabase::add_error_line(std::unique_ptr&& linestring, const char* error, osmium::object_id_type id) { 158 | m_srs.transform(linestring.get()); 159 | gdalcpp::Feature feature{m_layer_error_lines, std::move(linestring)}; 160 | feature.set_field("osm_id", std::to_string(id).c_str()); 161 | feature.set_field("error", error); 162 | feature.add_to_layer(); 163 | } 164 | 165 | void OutputDatabase::add_ring(std::unique_ptr&& polygon, osmium::object_id_type osm_id, unsigned int nways, unsigned int npoints, bool fixed) { 166 | m_srs.transform(polygon.get()); 167 | 168 | const bool land = polygon->getExteriorRing()->isClockwise(); 169 | const bool valid = polygon->IsValid(); 170 | 171 | if (!valid) { 172 | /* 173 | When the polygon is invalid we find out what and where the problem is. 174 | This code is a bit strange because older versions of the GEOS library 175 | only export this information as a string. We parse the reason and 176 | point coordinates (of a self-intersection-point for instance) from 177 | this string and create a point in the error layer for it. 178 | 179 | The exportToGEOS() method on OGR geometries is not documented. Let's 180 | hope that it will always be available. We use the GEOSisValidReason() 181 | function from the GEOS C interface to get to the reason. 182 | */ 183 | GEOSContextHandle_t contextHandle = OGRGeometry::createGEOSContext(); 184 | char* r = GEOSisValidReason(polygon->exportToGEOS(contextHandle)); 185 | std::string reason = r ? r : ""; 186 | OGRGeometry::freeGEOSContext(contextHandle); 187 | 188 | if (!reason.empty()) { 189 | const std::size_t left_bracket = reason.find('['); 190 | const std::size_t right_bracket = reason.find(']'); 191 | 192 | std::istringstream iss{reason.substr(left_bracket+1, right_bracket-left_bracket-1), std::istringstream::in}; 193 | double x = NAN; 194 | double y = NAN; 195 | iss >> x; 196 | iss >> y; 197 | reason.resize(left_bracket); 198 | 199 | auto point = std::make_unique(); 200 | point->assignSpatialReference(polygon->getSpatialReference()); 201 | point->setX(x); 202 | point->setY(y); 203 | 204 | if (reason == "Self-intersection") { 205 | reason = "self_intersection"; 206 | } 207 | add_error_point(std::move(point), reason.c_str(), osm_id); 208 | } else { 209 | std::cerr << "Did not get reason from GEOS why polygon " << osm_id << " is invalid. Could not write info to error points layer\n"; 210 | } 211 | } 212 | 213 | gdalcpp::Feature feature{m_layer_rings, std::move(polygon)}; 214 | feature.set_field("osm_id", static_cast(osm_id)); 215 | feature.set_field("nways", static_cast(nways)); 216 | feature.set_field("npoints", static_cast(npoints)); 217 | feature.set_field("fixed", fixed); 218 | feature.set_field("land", land); 219 | feature.set_field("valid", valid); 220 | feature.add_to_layer(); 221 | } 222 | 223 | void OutputDatabase::add_land_polygon(std::unique_ptr&& polygon) { 224 | m_srs.transform(polygon.get()); 225 | gdalcpp::Feature feature{m_layer_land_polygons, std::move(polygon)}; 226 | feature.add_to_layer(); 227 | } 228 | 229 | void OutputDatabase::add_water_polygon(std::unique_ptr&& polygon) { 230 | m_srs.transform(polygon.get()); 231 | gdalcpp::Feature feature{m_layer_water_polygons, std::move(polygon)}; 232 | feature.add_to_layer(); 233 | } 234 | 235 | void OutputDatabase::add_line(std::unique_ptr&& linestring) { 236 | m_srs.transform(linestring.get()); 237 | gdalcpp::Feature feature{m_layer_lines, std::move(linestring)}; 238 | feature.add_to_layer(); 239 | } 240 | 241 | std::vector OutputDatabase::layer_options() const { 242 | std::vector options; 243 | if (!m_with_index && (m_driver == "SQLite" || m_driver == "GPKG")) { 244 | options.emplace_back("SPATIAL_INDEX=no"); 245 | } 246 | return options; 247 | } 248 | 249 | std::vector OutputDatabase::driver_options() const { 250 | std::vector options; 251 | if (m_driver == "SQLite") { 252 | options.emplace_back("SPATIALITE=TRUE"); 253 | options.emplace_back("INIT_WITH_EPSG=no"); 254 | } 255 | return options; 256 | } 257 | -------------------------------------------------------------------------------- /src/output_database.hpp: -------------------------------------------------------------------------------- 1 | #ifndef OUTPUT_DATABASE_HPP 2 | #define OUTPUT_DATABASE_HPP 3 | 4 | /* 5 | 6 | Copyright 2012-2025 Jochen Topf . 7 | 8 | This file is part of OSMCoastline. 9 | 10 | OSMCoastline is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | OSMCoastline is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with OSMCoastline. If not, see . 22 | 23 | */ 24 | 25 | #include 26 | 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | class OGRLineString; 35 | class OGRPoint; 36 | class OGRPolygon; 37 | class SRS; 38 | 39 | struct Options; 40 | struct Stats; 41 | 42 | /** 43 | * Handle output to a database (via OGR). 44 | * Several tables/layers are created using the right SRS for the different 45 | * kinds of data. 46 | */ 47 | class OutputDatabase { 48 | 49 | std::string m_driver; 50 | 51 | bool m_with_index; 52 | 53 | SRS& m_srs; 54 | 55 | gdalcpp::Dataset m_dataset; 56 | 57 | // Any errors in a linestring 58 | gdalcpp::Layer m_layer_error_points; 59 | 60 | // Any errors in a point 61 | gdalcpp::Layer m_layer_error_lines; 62 | 63 | // Layer for polygon rings. 64 | // Will contain polygons without holes, ie. with just an outer ring. 65 | // Polygon outer rings will be oriented according to usual GIS custom with 66 | // points going clockwise around the ring, ie "land" is on the right hand 67 | // side of the border. This is the other way around from how it looks in 68 | // OSM. 69 | gdalcpp::Layer m_layer_rings; 70 | 71 | // Completed land polygons. 72 | gdalcpp::Layer m_layer_land_polygons; 73 | 74 | // Completed water polygons. 75 | gdalcpp::Layer m_layer_water_polygons; 76 | 77 | // Coastlines generated from completed polygons. 78 | // Lines contain at most max-points points. 79 | gdalcpp::Layer m_layer_lines; 80 | 81 | std::vector layer_options() const; 82 | 83 | std::vector driver_options() const; 84 | 85 | public: 86 | 87 | OutputDatabase(const std::string& driver, const std::string& outdb, SRS& srs, bool with_index=false); 88 | 89 | ~OutputDatabase() noexcept = default; 90 | 91 | void add_error_point(std::unique_ptr&& point, const char* error, osmium::object_id_type id = 0); 92 | void add_error_line(std::unique_ptr&& linestring, const char* error, osmium::object_id_type id = 0); 93 | void add_ring(std::unique_ptr&& polygon, osmium::object_id_type osm_id, unsigned int nways, unsigned int npoints, bool fixed); 94 | void add_land_polygon(std::unique_ptr&& polygon); 95 | void add_water_polygon(std::unique_ptr&& polygon); 96 | void add_line(std::unique_ptr&& linestring); 97 | 98 | void set_options(const Options& options); 99 | void set_meta(std::time_t runtime, int memory_usage, const Stats& stats); 100 | 101 | void commit(); 102 | 103 | }; // class OutputDatabase 104 | 105 | #endif // OUTPUT_DATABASE_HPP 106 | -------------------------------------------------------------------------------- /src/return_codes.hpp: -------------------------------------------------------------------------------- 1 | #ifndef OSMCOASTLINE_HPP 2 | #define OSMCOASTLINE_HPP 3 | 4 | /* 5 | 6 | Copyright 2012-2025 Jochen Topf . 7 | 8 | This file is part of OSMCoastline. 9 | 10 | OSMCoastline is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | OSMCoastline is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with OSMCoastline. If not, see . 22 | 23 | */ 24 | 25 | enum return_codes { 26 | return_code_ok = 0, 27 | return_code_warning = 1, 28 | return_code_error = 2, 29 | return_code_fatal = 3, 30 | return_code_cmdline = 4 31 | }; 32 | 33 | #endif // OSMCOASTLINE_HPP 34 | -------------------------------------------------------------------------------- /src/srs.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2012-2025 Jochen Topf . 4 | 5 | This file is part of OSMCoastline. 6 | 7 | OSMCoastline is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | OSMCoastline is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with OSMCoastline. If not, see . 19 | 20 | */ 21 | 22 | #include "srs.hpp" 23 | 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | bool SRS::set_output(int epsg) { 30 | auto const result = m_srs_out.importFromEPSG(epsg); 31 | if (result != OGRERR_NONE) { 32 | return false; 33 | } 34 | 35 | if (epsg != 4326) { 36 | m_transform = std::unique_ptr(OGRCreateCoordinateTransformation(&m_srs_wgs84, &m_srs_out)); 37 | if (!m_transform) { 38 | return false; 39 | } 40 | } 41 | 42 | return true; 43 | } 44 | 45 | void SRS::transform(OGRGeometry* geometry) { 46 | if (!m_transform) { // Output SRS is WGS84, no transformation needed. 47 | return; 48 | } 49 | 50 | // Transform if no SRS is set on input geometry or it is set to WGS84. 51 | const OGRSpatialReference* srs = geometry->getSpatialReference(); 52 | if (srs == nullptr || srs->IsSame(&m_srs_wgs84)) { 53 | auto const result = geometry->transform(m_transform.get()); 54 | if (result != OGRERR_NONE) { 55 | throw TransformationException{result}; 56 | } 57 | } 58 | } 59 | 60 | OGREnvelope SRS::max_extent() const { 61 | OGREnvelope envelope; 62 | 63 | if (is_wgs84()) { 64 | envelope.MinX = -180.0; 65 | envelope.MinY = -90.0; 66 | envelope.MaxX = 180.0; 67 | envelope.MaxY = 90.0; 68 | } else { 69 | envelope.MinX = -20037508.342789244; 70 | envelope.MinY = -20037508.342789244; 71 | envelope.MaxX = 20037508.342789244; 72 | envelope.MaxY = 20037508.342789244; 73 | } 74 | 75 | return envelope; 76 | } 77 | 78 | -------------------------------------------------------------------------------- /src/srs.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRS_HPP 2 | #define SRS_HPP 3 | 4 | /* 5 | 6 | Copyright 2012-2025 Jochen Topf . 7 | 8 | This file is part of OSMCoastline. 9 | 10 | OSMCoastline is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | OSMCoastline is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with OSMCoastline. If not, see . 22 | 23 | */ 24 | 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | class OGRGeometry; 31 | class OGREnvelope; 32 | 33 | class SRS { 34 | 35 | /// WGS84 (input) SRS. 36 | OGRSpatialReference m_srs_wgs84; 37 | 38 | /// Output SRS. 39 | OGRSpatialReference m_srs_out; 40 | 41 | /** 42 | * If the output SRS is not WGS84, this contains the transformation 43 | * object. Otherwise nullptr. 44 | */ 45 | std::unique_ptr m_transform; 46 | 47 | public: 48 | 49 | /** 50 | * This exception is thrown when a transformation to the output SRS fails. 51 | */ 52 | class TransformationException : public std::runtime_error { 53 | 54 | public: 55 | 56 | explicit TransformationException(OGRErr error_code) : 57 | runtime_error("SRS transformation failed - OGRErr=" + std::to_string(error_code)) { 58 | } 59 | 60 | }; // class TransformationException 61 | 62 | SRS() { 63 | /* This has to be "CRS84" not "WGS84", because WGS84 has the axis 64 | * order reversed in GDAL 3 compared to GDAL 2. For CRS84 the axis 65 | * order is correct in both GDAL versions. 66 | * See https://gdal.org/tutorials/osr_api_tut.html */ 67 | auto const result = m_srs_wgs84.SetWellKnownGeogCS("CRS84"); 68 | if (result != OGRERR_NONE) { 69 | throw TransformationException{result}; 70 | } 71 | } 72 | 73 | /** 74 | * Set output SRS to EPGS code. Call this method before using any 75 | * of the other methods of this object. 76 | */ 77 | bool set_output(int epsg); 78 | 79 | bool is_wgs84() const noexcept { 80 | return !m_transform; 81 | } 82 | 83 | OGRSpatialReference* wgs84() { 84 | return &m_srs_wgs84; 85 | } 86 | 87 | OGRSpatialReference* out() { 88 | return &m_srs_out; 89 | } 90 | 91 | /** 92 | * Transform geometry to output SRS (if it is not in the output SRS 93 | * already). 94 | */ 95 | void transform(OGRGeometry* geometry); 96 | 97 | /** 98 | * Return max extent for output SRS. 99 | */ 100 | OGREnvelope max_extent() const; 101 | 102 | /** 103 | * These values are used to decide which coastline segments are 104 | * bogus. They are near the antimeridian or southern edge of the 105 | * map and only there to close the coastline polygons. 106 | */ 107 | double max_x() const noexcept { 108 | return is_wgs84() ? 179.9999 : 20037500.0; 109 | } 110 | 111 | double min_x() const noexcept { 112 | return is_wgs84() ? -179.9999 : -20037500.0; 113 | } 114 | 115 | double min_y() const noexcept { 116 | return is_wgs84() ? -85.049 : -20037400.0; 117 | } 118 | 119 | }; // class SRS 120 | 121 | #endif // SRS_HPP 122 | -------------------------------------------------------------------------------- /src/stats.hpp: -------------------------------------------------------------------------------- 1 | #ifndef STATS_HPP 2 | #define STATS_HPP 3 | 4 | /* 5 | 6 | Copyright 2012-2025 Jochen Topf . 7 | 8 | This file is part of OSMCoastline. 9 | 10 | OSMCoastline is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | OSMCoastline is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with OSMCoastline. If not, see . 22 | 23 | */ 24 | 25 | struct Stats { 26 | unsigned int ways = 0; 27 | unsigned int unconnected_nodes = 0; 28 | unsigned int rings = 0; 29 | unsigned int rings_from_single_way = 0; 30 | unsigned int rings_fixed = 0; 31 | unsigned int rings_turned_around = 0; 32 | unsigned int land_polygons_before_split = 0; 33 | unsigned int land_polygons_after_split = 0; 34 | }; 35 | 36 | #endif // STATS_HPP 37 | -------------------------------------------------------------------------------- /src/util.hpp: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_HPP 2 | #define UTIL_HPP 3 | 4 | /* 5 | 6 | Copyright 2012-2025 Jochen Topf . 7 | 8 | This file is part of OSMCoastline. 9 | 10 | OSMCoastline is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | OSMCoastline is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with OSMCoastline. If not, see . 22 | 23 | */ 24 | 25 | #include 26 | #include 27 | 28 | template 29 | std::unique_ptr make_unique_ptr_clone(const T* source) { 30 | static_assert(std::is_convertible::value, "T* must be convertible to R*"); 31 | return std::unique_ptr{static_cast(source->clone())}; 32 | } 33 | 34 | template 35 | std::unique_ptr static_cast_unique_ptr(std::unique_ptr&& ptr) { 36 | static_assert(std::is_base_of::value, "TDerived must be derived from TBase"); 37 | return std::unique_ptr{static_cast(ptr.release())}; 38 | } 39 | 40 | 41 | #endif // UTIL_HPP 42 | -------------------------------------------------------------------------------- /src/version.cpp.in: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | const char* get_osmcoastline_version() noexcept { 5 | return "@PROJECT_VERSION@"; 6 | } 7 | 8 | const char* get_osmcoastline_long_version() noexcept { 9 | return "version @PROJECT_VERSION@@VERSION_FROM_GIT@"; 10 | } 11 | 12 | const char* get_libosmium_version() noexcept { 13 | return "libosmium version " LIBOSMIUM_VERSION_STRING; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/version.hpp: -------------------------------------------------------------------------------- 1 | #ifndef VERSION_HPP 2 | #define VERSION_HPP 3 | 4 | const char* get_osmcoastline_version() noexcept; 5 | const char* get_osmcoastline_long_version() noexcept; 6 | const char* get_libosmium_version() noexcept; 7 | 8 | #endif // VERSION_HPP 9 | -------------------------------------------------------------------------------- /taginfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "data_format": 1, 3 | "data_url": "https://raw.githubusercontent.com/osmcode/osmcoastline/master/taginfo.json", 4 | "project": { 5 | "name": "OSMCoastline", 6 | "description": "Program that assembles continuous coastlines from OSM data and creates land and water polygons.", 7 | "project_url": "https://wiki.osm.org/wiki/OSMCoastline", 8 | "doc_url": "https://github.com/osmcode/osmcoastline", 9 | "icon_url": "https://osmcode.org/img/logo-osmcoastline.svg", 10 | "contact_name": "Jochen Topf", 11 | "contact_email": "jochen@topf.org", 12 | "keywords": [ 13 | "export" 14 | ] 15 | }, 16 | "tags": [ 17 | { 18 | "key": "natural", 19 | "value": "coastline", 20 | "object_types": [ "way" ], 21 | "description": "The coastline." 22 | }, 23 | { 24 | "key": "coastline", 25 | "value": "bogus", 26 | "object_types": [ "way" ], 27 | "description": "Pseudo-coastline in Antarctica tagged for technical reasons." 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #----------------------------------------------------------------------------- 2 | # 3 | # CMake Config 4 | # 5 | # OSMCoastline Tests 6 | # 7 | #----------------------------------------------------------------------------- 8 | 9 | file(GLOB TEST_SCRIPTS t/*.sh) 10 | 11 | list(SORT TEST_SCRIPTS) 12 | 13 | foreach(file ${TEST_SCRIPTS}) 14 | string(REGEX REPLACE "^.*/(.+).sh$" "\\1" tid ${file}) 15 | 16 | message(STATUS "Adding test ${tid}") 17 | add_test(NAME test-${tid}-4326 18 | COMMAND ${file} ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${tid} 4326) 19 | add_test(NAME test-${tid}-3857 20 | COMMAND ${file} ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${tid} 3857) 21 | endforeach() 22 | 23 | 24 | #----------------------------------------------------------------------------- 25 | -------------------------------------------------------------------------------- /test/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | readonly SRC_DIR=$1 4 | readonly BIN_DIR=$2 5 | readonly TEST_ID=$3 6 | readonly SRID=$4 7 | 8 | readonly OSMC=${BIN_DIR}/src/osmcoastline 9 | readonly INPUT=${BIN_DIR}/test/${TEST_ID}-${SRID}.opl 10 | readonly LOG=${BIN_DIR}/test/${TEST_ID}-${SRID}.log 11 | readonly DB=${BIN_DIR}/test/${TEST_ID}-${SRID}.db 12 | readonly DUMP=${BIN_DIR}/test/${TEST_ID}-${SRID}.dump 13 | readonly SQL="spatialite -bail -batch $DB" 14 | 15 | check_count_with_op() { 16 | test "$(echo "SELECT count(*) FROM $1;" | $SQL)" "$2" "$3" 17 | } 18 | 19 | check_count() { 20 | check_count_with_op "$1" "-eq" "$2" 21 | } 22 | 23 | -------------------------------------------------------------------------------- /test/t/gdal-driver-gpkg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # Select "GPKG" as the GDAL driver, and output data format. 5 | # 6 | #----------------------------------------------------------------------------- 7 | 8 | # shellcheck source=test/init.sh 9 | . "$1/test/init.sh" 10 | 11 | set -x 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | cat <<'OSM' >"$INPUT" 16 | n100 v1 x10.01 y1.01 17 | n101 v1 x10.04 y1.01 18 | n102 v1 x10.04 y1.04 19 | n103 v1 x10.01 y1.04 20 | w200 v1 Tnatural=coastline Nn100,n101,n102,n103,n100 21 | OSM 22 | 23 | #----------------------------------------------------------------------------- 24 | 25 | set -e 26 | 27 | rm -rf "$DB" 28 | 29 | "$OSMC" --verbose --overwrite --gdal-driver "GPKG" --srs="$SRID" --output-database="$DB" "$INPUT" >"$LOG" 2>&1 30 | 31 | test $? -eq 0 32 | 33 | grep 'Turned 0 polygons around.$' "$LOG" 34 | 35 | grep '^There were 0 warnings.$' "$LOG" 36 | grep '^There were 0 errors.$' "$LOG" 37 | 38 | check_count land_polygons 1; 39 | check_count error_points 0; 40 | check_count error_lines 0; 41 | 42 | #----------------------------------------------------------------------------- 43 | -------------------------------------------------------------------------------- /test/t/gdal-driver-shapefile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # Select "ESRI Shapefile" as the GDAL driver, and output data format. 5 | # 6 | #----------------------------------------------------------------------------- 7 | 8 | # shellcheck source=test/init.sh 9 | . "$1/test/init.sh" 10 | 11 | set -x 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | cat <<'OSM' >"$INPUT" 16 | n100 v1 x1.01 y1.01 17 | n101 v1 x1.04 y1.01 18 | n102 v1 x1.04 y1.04 19 | n103 v1 x1.01 y1.04 20 | w200 v1 Tnatural=coastline Nn100,n101,n102,n103,n100 21 | OSM 22 | 23 | #----------------------------------------------------------------------------- 24 | 25 | set -e 26 | 27 | rm -rf "$DB" 28 | 29 | "$OSMC" --verbose --overwrite --gdal-driver "ESRI Shapefile" --srs="$SRID" --output-database="$DB" "$INPUT" >"$LOG" 2>&1 30 | 31 | test $? -eq 0 32 | 33 | grep 'Turned 0 polygons around.$' "$LOG" 34 | 35 | grep '^There were 0 warnings.$' "$LOG" 36 | grep '^There were 0 errors.$' "$LOG" 37 | 38 | test -d "$DB" 39 | test -f "$DB/land_polygons.dbf" 40 | test -f "$DB/land_polygons.prj" 41 | test -f "$DB/land_polygons.shp" 42 | test -f "$DB/land_polygons.shx" 43 | 44 | #----------------------------------------------------------------------------- 45 | -------------------------------------------------------------------------------- /test/t/invalid-complex-overlap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # This is an especially complex case of the coastline looping back on 5 | # itself. 6 | # 7 | #----------------------------------------------------------------------------- 8 | 9 | # shellcheck source=test/init.sh 10 | . "$1/test/init.sh" 11 | 12 | set -x 13 | 14 | #----------------------------------------------------------------------------- 15 | 16 | cat <<'OSM' >"$INPUT" 17 | n100 v1 dV c0 t i0 u T x1.00 y1.07 18 | n101 v1 dV c0 t i0 u T x1.00 y1.06 19 | n102 v1 dV c0 t i0 u T x1.00 y1.03 20 | n103 v1 dV c0 t i0 u T x1.00 y1.02 21 | n104 v1 dV c0 t i0 u T x1.00 y1.05 22 | n105 v1 dV c0 t i0 u T x1.00 y1.04 23 | w200 v1 dV c0 t i0 u Tnatural=coastline Nn101,n100 24 | w201 v1 dV c0 t i0 u Tnatural=coastline Nn100,n101,n104 25 | w202 v1 dV c0 t i0 u Tnatural=coastline Nn103,n102,n105 26 | w203 v1 dV c0 t i0 u Tnatural=coastline Nn105,n104 27 | w204 v1 dV c0 t i0 u Tnatural=coastline Nn104,n105 28 | w205 v1 dV c0 t i0 u Tnatural=coastline Nn104,n101 29 | OSM 30 | 31 | #----------------------------------------------------------------------------- 32 | 33 | "$OSMC" --verbose --overwrite --srs="$SRID" --output-database="$DB" "$INPUT" >"$LOG" 2>&1 34 | RC=$? 35 | set -e 36 | 37 | test $RC -eq 2 38 | 39 | grep '^There were 3 warnings.$' "$LOG" 40 | grep '^There were 2 errors.$' "$LOG" 41 | 42 | check_count land_polygons 0; 43 | check_count error_points 2; 44 | check_count error_lines 4; 45 | 46 | #----------------------------------------------------------------------------- 47 | -------------------------------------------------------------------------------- /test/t/invalid-direction.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # Invalid small "island" with coastline going the wrong direction. 5 | # 6 | #----------------------------------------------------------------------------- 7 | 8 | # shellcheck source=test/init.sh 9 | . "$1/test/init.sh" 10 | 11 | set -x 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | cat <<'OSM' >"$INPUT" 16 | n100 v1 x1.01 y1.01 17 | n101 v1 x1.01 y1.04 18 | n102 v1 x1.04 y1.04 19 | n103 v1 x1.04 y1.01 20 | w200 v1 Tnatural=coastline Nn100,n101,n102,n103,n100 21 | OSM 22 | 23 | #----------------------------------------------------------------------------- 24 | 25 | "$OSMC" --verbose --overwrite --srs="$SRID" --output-database="$DB" "$INPUT" >"$LOG" 2>&1 26 | RC=$? 27 | set -e 28 | 29 | test $RC -eq 1 30 | 31 | grep 'Turned 1 polygons around.$' "$LOG" 32 | 33 | grep '^There were 1 warnings.$' "$LOG" 34 | grep '^There were 0 errors.$' "$LOG" 35 | 36 | check_count land_polygons 1; 37 | check_count error_points 0; 38 | check_count error_lines 1; 39 | 40 | echo "SELECT InsertEpsgSrid(4326);" | $SQL 41 | 42 | echo "SELECT AsText(Transform(geometry, 4326)) FROM land_polygons;" | $SQL \ 43 | | grep -F 'POLYGON((1.01 1.01, 1.01 1.04, 1.04 1.04, 1.04 1.01, 1.01 1.01))' 44 | 45 | echo "SELECT AsText(Transform(geometry, 4326)), osm_id, error FROM error_lines;" | $SQL \ 46 | | grep -F 'LINESTRING(1.01 1.01, 1.01 1.04, 1.04 1.04, 1.04 1.01, 1.01 1.01)|0|direction' 47 | 48 | #----------------------------------------------------------------------------- 49 | -------------------------------------------------------------------------------- /test/t/invalid-duplicate-segments-1.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # Correct coastline created from duplicate segments 5 | # 6 | #----------------------------------------------------------------------------- 7 | 8 | # shellcheck source=test/init.sh 9 | . "$1/test/init.sh" 10 | 11 | set -x 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | cat <<'OSM' >"$INPUT" 16 | n100 v1 x1.10 y1.06 17 | n101 v1 x1.30 y1.06 18 | n102 v1 x1.35 y1.05 19 | n103 v1 x1.30 y1.04 20 | n104 v1 x1.25 y1.04 21 | n105 v1 x1.20 y1.04 22 | n106 v1 x1.15 y1.04 23 | n107 v1 x1.10 y1.04 24 | n108 v1 x1.05 y1.05 25 | w200 v1 Tnatural=coastline Nn106,n105,n104 26 | w201 v1 Tnatural=coastline Nn106,n105,n104 27 | w202 v1 Tnatural=coastline Nn104,n103,n102,n101,n100,n108,n107,n106 28 | OSM 29 | 30 | #----------------------------------------------------------------------------- 31 | 32 | "$OSMC" --verbose --overwrite --srs="$SRID" --output-database="$DB" "$INPUT" >"$LOG" 2>&1 33 | RC=$? 34 | set -e 35 | 36 | test $RC -eq 1 37 | 38 | grep 'Turned 0 polygons around.$' "$LOG" 39 | 40 | if [ "$SRID" = "4326" ]; then 41 | grep '^There were 3 warnings.$' "$LOG" 42 | check_count error_lines 3; 43 | else 44 | # "questionables" are not checked in 3857 45 | grep '^There were 2 warnings.$' "$LOG" 46 | check_count error_lines 2; 47 | fi 48 | 49 | grep '^There were 0 errors.$' "$LOG" 50 | 51 | check_count land_polygons 1; 52 | check_count error_points 0; 53 | 54 | echo "SELECT InsertEpsgSrid(4326);" | $SQL 55 | 56 | echo "SELECT AsText(Transform(geometry, 4326)), osm_id, error FROM error_lines;" | $SQL >"$DUMP" 57 | 58 | grep -F 'LINESTRING(1.15 1.04, 1.2 1.04)|0|overlap' "$DUMP" 59 | grep -F 'LINESTRING(1.2 1.04, 1.25 1.04)|0|overlap' "$DUMP" 60 | 61 | if [ "$SRID" = "4326" ]; then 62 | grep -F 'LINESTRING(1.15 1.04, 1.2 1.04, 1.25 1.04, 1.3 1.04, 1.35 1.05, 1.3 1.06, 1.1 1.06, 1.05 1.05, 1.1 1.04, 1.15 1.04)|201|questionable' "$DUMP" 63 | fi 64 | 65 | echo "SELECT AsText(Transform(geometry, 4326)) FROM land_polygons;" | $SQL \ 66 | | grep -F 'POLYGON((1.15 1.04, 1.1 1.04, 1.05 1.05, 1.1 1.06, 1.3 1.06, 1.35 1.05, 1.3 1.04, 1.25 1.04, 1.2 1.04, 1.15 1.04))' 67 | 68 | #----------------------------------------------------------------------------- 69 | -------------------------------------------------------------------------------- /test/t/invalid-duplicate-segments-2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # Duplicate coastline 5 | # 6 | #----------------------------------------------------------------------------- 7 | 8 | # shellcheck source=test/init.sh 9 | . "$1/test/init.sh" 10 | 11 | set -x 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | "$BIN_DIR/src/nodegrid2opl" << 'NODES' >"$INPUT" 16 | 0--1--2 17 | NODES 18 | 19 | cat <<'OSM' >>"$INPUT" 20 | w200 v1 Tnatural=coastline Nn100,n102 21 | w201 v1 Tnatural=coastline Nn100,n101 22 | OSM 23 | 24 | #----------------------------------------------------------------------------- 25 | 26 | "$OSMC" --verbose --overwrite --srs="$SRID" --output-database="$DB" "$INPUT" >"$LOG" 2>&1 27 | RC=$? 28 | set -e 29 | 30 | test $RC -eq 2 31 | 32 | grep 'There are 3 nodes where the coastline is not closed.$' "$LOG" 33 | 34 | grep '^There were 0 warnings.$' "$LOG" 35 | grep '^There were 3 errors.$' "$LOG" 36 | 37 | check_count land_polygons 0; 38 | check_count error_points 2; 39 | check_count error_lines 1; 40 | 41 | #----------------------------------------------------------------------------- 42 | -------------------------------------------------------------------------------- /test/t/invalid-node-id-mismatch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # Node IDs don't match but same location 5 | # 6 | #----------------------------------------------------------------------------- 7 | 8 | # shellcheck source=test/init.sh 9 | . "$1/test/init.sh" 10 | 11 | set -x 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | cat <<'OSM' >"$INPUT" 16 | n100 v1 x1.00 y1.00 17 | n101 v1 x1.00 y1.01 18 | n102 v1 x1.01 y1.01 19 | n103 v1 x1.01 y1.00 20 | n104 v1 x1.00 y1.00 21 | w200 v1 Tnatural=coastline Nn100,n101,n102 22 | w201 v1 Tnatural=coastline Nn102,n103,n104 23 | OSM 24 | 25 | #----------------------------------------------------------------------------- 26 | 27 | "$OSMC" --verbose --overwrite --srs="$SRID" --output-database="$DB" "$INPUT" >"$LOG" 2>&1 28 | RC=$? 29 | set -e 30 | 31 | test $RC -eq 2 32 | 33 | grep 'There are 2 nodes where the coastline is not closed.$' "$LOG" 34 | 35 | grep '^There were 0 warnings.$' "$LOG" 36 | grep '^There were 2 errors.$' "$LOG" 37 | 38 | check_count land_polygons 0; 39 | check_count error_points 2; 40 | check_count error_lines 0; 41 | 42 | echo "SELECT InsertEpsgSrid(4326);" | $SQL 43 | 44 | echo "SELECT AsText(Transform(geometry, 4326)), osm_id, error FROM error_points;" | $SQL >"$DUMP" 45 | 46 | grep -F 'POINT(1 1)|100|fixed_end_point' "$DUMP" 47 | grep -F 'POINT(1 1)|104|fixed_end_point' "$DUMP" 48 | 49 | #----------------------------------------------------------------------------- 50 | -------------------------------------------------------------------------------- /test/t/invalid-node-with-coastline-tag.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # Single node with coastline tag is flagged as error. 5 | # 6 | #----------------------------------------------------------------------------- 7 | 8 | # shellcheck source=test/init.sh 9 | . "$1/test/init.sh" 10 | 11 | set -x 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | cat <<'OSM' >"$INPUT" 16 | n100 x1.01 y1.01 Tnatural=coastline 17 | OSM 18 | 19 | #----------------------------------------------------------------------------- 20 | 21 | "$OSMC" --verbose --overwrite --srs="$SRID" --output-database="$DB" "$INPUT" >"$LOG" 2>&1 22 | RC=$? 23 | set -e 24 | 25 | test $RC -eq 2 26 | 27 | grep 'No polygons created!$' "$LOG" 28 | 29 | grep '^There were 0 warnings.$' "$LOG" 30 | grep '^There were 1 errors.$' "$LOG" 31 | 32 | check_count land_polygons 0; 33 | check_count error_points 1; 34 | check_count error_lines 0; 35 | 36 | echo "SELECT InsertEpsgSrid(4326);" | $SQL 37 | 38 | echo "SELECT AsText(Transform(geometry, 4326)), osm_id, error FROM error_points;" | $SQL \ 39 | | grep -F 'POINT(1.01 1.01)|100|tagged_node' 40 | 41 | #----------------------------------------------------------------------------- 42 | -------------------------------------------------------------------------------- /test/t/invalid-part-reversed.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # Part of the coastline is reversed. 5 | # 6 | #----------------------------------------------------------------------------- 7 | 8 | # shellcheck source=test/init.sh 9 | . "$1/test/init.sh" 10 | 11 | set -x 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | cat <<'OSM' >"$INPUT" 16 | n100 v1 x1.10 y1.06 17 | n101 v1 x1.30 y1.06 18 | n102 v1 x1.35 y1.05 19 | n103 v1 x1.30 y1.04 20 | n104 v1 x1.25 y1.04 21 | n105 v1 x1.20 y1.04 22 | n106 v1 x1.15 y1.04 23 | n107 v1 x1.10 y1.04 24 | n108 v1 x1.05 y1.05 25 | w200 v1 Tnatural=coastline Nn104,n105,n106 26 | w201 v1 Tnatural=coastline Nn104,n103,n102,n101,n100,n108,n107,n106 27 | OSM 28 | 29 | #----------------------------------------------------------------------------- 30 | 31 | "$OSMC" --verbose --overwrite --srs="$SRID" --output-database="$DB" "$INPUT" >"$LOG" 2>&1 32 | RC=$? 33 | set -e 34 | 35 | test $RC -eq 2 36 | 37 | grep 'There are 2 nodes where the coastline is not closed.$' "$LOG" 38 | 39 | if [ "$SRID" = "4326" ]; then 40 | grep '^There were 1 warnings.$' "$LOG" 41 | check_count error_lines 2; 42 | else 43 | # "questionables" are not checked in 3857 44 | grep '^There were 0 warnings.$' "$LOG" 45 | check_count error_lines 1; 46 | fi 47 | 48 | grep '^There were 1 errors.$' "$LOG" 49 | 50 | check_count land_polygons 1; 51 | check_count error_points 2; 52 | 53 | echo "SELECT InsertEpsgSrid(4326);" | $SQL 54 | 55 | echo "SELECT AsText(Transform(geometry, 4326)) FROM land_polygons;" | $SQL \ 56 | | grep -F 'POLYGON((1.25 1.04, 1.15 1.04, 1.1 1.04, 1.05 1.05, 1.1 1.06, 1.3 1.06, 1.35 1.05, 1.3 1.04, 1.25 1.04))' 57 | 58 | #----------------------------------------------------------------------------- 59 | -------------------------------------------------------------------------------- /test/t/invalid-ring-not-closed.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # Invalid small "island" with coastline ring not closed. 5 | # 6 | #----------------------------------------------------------------------------- 7 | 8 | # shellcheck source=test/init.sh 9 | . "$1/test/init.sh" 10 | 11 | set -x 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | cat <<'OSM' >"$INPUT" 16 | n100 v1 x1.01 y1.01 17 | n101 v1 x1.04 y1.01 18 | n102 v1 x1.04 y1.04 19 | n103 v1 x1.01 y1.04 20 | w200 v1 Tnatural=coastline Nn100,n101,n102,n103 21 | OSM 22 | 23 | #----------------------------------------------------------------------------- 24 | 25 | "$OSMC" --verbose --overwrite --srs="$SRID" --output-database="$DB" --output-rings "$INPUT" >"$LOG" 2>&1 26 | RC=$? 27 | set -e 28 | 29 | test $RC -eq 2 30 | 31 | grep 'There are 2 nodes where the coastline is not closed.' "$LOG" 32 | grep 'Closed 1 rings. This left 0 nodes where the coastline could not be closed.' "$LOG" 33 | grep '^There were 0 warnings.$' "$LOG" 34 | grep '^There were 1 errors.$' "$LOG" 35 | 36 | check_count land_polygons 1; 37 | check_count rings 1; 38 | check_count error_points 2; 39 | check_count error_lines 1; 40 | 41 | echo "SELECT InsertEpsgSrid(4326);" | $SQL 42 | 43 | echo "SELECT AsText(Transform(geometry, 4326)) FROM land_polygons;" | $SQL \ 44 | | grep -F 'POLYGON((1.01 1.01, 1.01 1.04, 1.04 1.04, 1.04 1.01, 1.01 1.01))' 45 | 46 | echo "SELECT AsText(Transform(geometry, 4326)), osm_id, error FROM error_points;" | $SQL >"$DUMP" 47 | 48 | grep -F 'POINT(1.01 1.01)|100|fixed_end_point' "$DUMP" 49 | grep -F 'POINT(1.01 1.04)|103|fixed_end_point' "$DUMP" 50 | 51 | echo "SELECT AsText(Transform(geometry, 4326)), osm_id, error FROM error_lines;" | $SQL \ 52 | | grep -F 'LINESTRING(1.01 1.04, 1.01 1.01)|0|added_line' 53 | 54 | #----------------------------------------------------------------------------- 55 | 56 | set +e 57 | 58 | "$OSMC" --verbose --overwrite --srs="$SRID" --output-database="$DB" --output-rings -c 0 "$INPUT" >"$LOG" 2>&1 59 | RC=$? 60 | set -e 61 | 62 | test $RC -eq 2 63 | 64 | grep 'There are 2 nodes where the coastline is not closed.' "$LOG" 65 | grep 'No polygons created!' "$LOG" 66 | grep '^There were 1 warnings.$' "$LOG" 67 | grep '^There were 1 errors.$' "$LOG" 68 | 69 | check_count land_polygons 0; 70 | check_count rings 0; 71 | check_count error_points 2; 72 | check_count error_lines 1; 73 | 74 | echo "SELECT InsertEpsgSrid(4326);" | $SQL 75 | 76 | echo "SELECT AsText(Transform(geometry, 4326)), osm_id, error FROM error_lines;" | $SQL \ 77 | | grep -F 'LINESTRING(1.01 1.04, 1.04 1.04, 1.04 1.01, 1.01 1.01)|200|not_closed' 78 | 79 | #----------------------------------------------------------------------------- 80 | -------------------------------------------------------------------------------- /test/t/invalid-self-intersection-on-closed-ring-one-way.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # Self-intersection (closed ring) 5 | # 6 | #----------------------------------------------------------------------------- 7 | 8 | # shellcheck source=test/init.sh 9 | . "$1/test/init.sh" 10 | 11 | set -x 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | "$BIN_DIR/src/nodegrid2opl" << 'NODES' >"$INPUT" 16 | 0 8 17 | 4 18 | 5 3 19 | 2 6 7 20 | 1 21 | NODES 22 | 23 | cat <<'OSM' >>"$INPUT" 24 | w200 v1 Tnatural=coastline Nn100,n101,n102,n103,n104,n105,n106,n107,n108,n100 25 | OSM 26 | 27 | #----------------------------------------------------------------------------- 28 | 29 | "$OSMC" --verbose --overwrite --srs="$SRID" --output-database="$DB" "$INPUT" >"$LOG" 2>&1 30 | RC=$? 31 | set -e 32 | 33 | test $RC -eq 1 34 | 35 | grep 'Self-intersection at or near point' "$LOG" 36 | 37 | grep '^There were 1 warnings.$' "$LOG" 38 | grep '^There were 0 errors.$' "$LOG" 39 | 40 | check_count land_polygons 1; 41 | check_count error_points 1; 42 | check_count error_lines 0; 43 | 44 | echo "SELECT InsertEpsgSrid(4326);" | $SQL 45 | 46 | echo "SELECT AsText(Transform(geometry, 4326)), osm_id, error FROM error_points;" | $SQL \ 47 | | grep -F 'POINT(1.09 1.975)|0|intersection' 48 | 49 | #----------------------------------------------------------------------------- 50 | -------------------------------------------------------------------------------- /test/t/invalid-self-intersection-on-closed-ring-two-ways.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # Self-intersection (closed ring, two ways) 5 | # 6 | #----------------------------------------------------------------------------- 7 | 8 | # shellcheck source=test/init.sh 9 | . "$1/test/init.sh" 10 | 11 | set -x 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | "$BIN_DIR/src/nodegrid2opl" << 'NODES' >"$INPUT" 16 | 0 8 17 | 4 18 | 5 3 19 | 2 6 7 20 | 1 21 | NODES 22 | 23 | cat <<'OSM' >>"$INPUT" 24 | w200 v1 Tnatural=coastline Nn103,n104,n105,n106,n107,n108 25 | w200 v1 Tnatural=coastline Nn108,n100,n101,n102,n103 26 | OSM 27 | 28 | #----------------------------------------------------------------------------- 29 | 30 | "$OSMC" --verbose --overwrite --srs="$SRID" --output-database="$DB" "$INPUT" >"$LOG" 2>&1 31 | RC=$? 32 | set -e 33 | 34 | test $RC -eq 1 35 | 36 | grep 'Self-intersection at or near point' "$LOG" 37 | 38 | grep '^There were 1 warnings.$' "$LOG" 39 | grep '^There were 0 errors.$' "$LOG" 40 | 41 | check_count land_polygons 1; 42 | check_count error_points 1; 43 | check_count error_lines 0; 44 | 45 | echo "SELECT InsertEpsgSrid(4326);" | $SQL 46 | 47 | echo "SELECT AsText(Transform(geometry, 4326)), osm_id, error FROM error_points;" | $SQL \ 48 | | grep -F 'POINT(1.09 1.975)|0|intersection' 49 | 50 | #----------------------------------------------------------------------------- 51 | -------------------------------------------------------------------------------- /test/t/invalid-self-intersection-on-open-ring.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # Self-intersection (open ring) 5 | # 6 | #----------------------------------------------------------------------------- 7 | 8 | # shellcheck source=test/init.sh 9 | . "$1/test/init.sh" 10 | 11 | set -x 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | "$BIN_DIR/src/nodegrid2opl" << 'NODES' >"$INPUT" 16 | 17 | 4 18 | 5 3 19 | 2 6 7 20 | 1 21 | 22 | NODES 23 | 24 | cat <<'OSM' >>"$INPUT" 25 | w200 v1 Tnatural=coastline Nn101,n102,n103,n104,n105,n106,n107 26 | OSM 27 | 28 | #----------------------------------------------------------------------------- 29 | 30 | "$OSMC" --verbose --overwrite --srs="$SRID" --output-database="$DB" "$INPUT" >"$LOG" 2>&1 31 | RC=$? 32 | set -e 33 | 34 | test $RC -eq 2 35 | 36 | grep 'Self-intersection at or near point' "$LOG" 37 | 38 | grep '^There were [12] warnings.$' "$LOG" 39 | grep '^There were 1 errors.$' "$LOG" 40 | 41 | check_count land_polygons 1; 42 | check_count error_points 3; 43 | check_count_with_op error_lines -ge 1; 44 | check_count_with_op error_lines -le 2; 45 | 46 | echo "SELECT InsertEpsgSrid(4326);" | $SQL 47 | 48 | echo "SELECT AsText(Transform(geometry, 4326)), osm_id, error FROM error_points;" | $SQL >"$DUMP" 49 | 50 | grep -F 'POINT(1.09 1.975)|0|intersection' "$DUMP" 51 | grep -F 'POINT(1.05 1.96)|101|fixed_end_point' "$DUMP" 52 | grep -F 'POINT(1.15 1.97)|107|fixed_end_point' "$DUMP" 53 | 54 | #----------------------------------------------------------------------------- 55 | -------------------------------------------------------------------------------- /test/t/overlapping-islands.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # Overlapping islands 5 | # 6 | #----------------------------------------------------------------------------- 7 | 8 | # shellcheck source=test/init.sh 9 | . "$1/test/init.sh" 10 | 11 | set -x 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | "$BIN_DIR/src/nodegrid2opl" << 'NODES' >"$INPUT" 16 | 17 | 0 1 18 | 19 | 2 3 20 | 8 9 21 | 4 5 22 | a b 23 | 6 7 24 | 25 | NODES 26 | 27 | cat <<'OSM' >>"$INPUT" 28 | w200 v1 Tnatural=coastline Nn100,n102,n104,n106,n107,n105,n103,n101,n100 29 | w200 v1 Tnatural=coastline Nn108,n110,n111,n109,n108 30 | OSM 31 | 32 | #----------------------------------------------------------------------------- 33 | 34 | "$OSMC" --verbose --overwrite --srs="$SRID" --output-database="$DB" "$INPUT" >"$LOG" 2>&1 35 | RC=$? 36 | set -e 37 | 38 | test $RC -eq 1 39 | 40 | grep '^There were 2 warnings.$' "$LOG" 41 | grep '^There were 0 errors.$' "$LOG" 42 | 43 | check_count land_polygons 2; 44 | check_count error_points 2; 45 | check_count error_lines 0; 46 | 47 | echo "SELECT InsertEpsgSrid(4326);" | $SQL 48 | 49 | echo "SELECT AsText(Transform(geometry, 4326)), osm_id, error FROM error_points;" | $SQL >"$DUMP" 50 | 51 | grep -F 'POINT(1.145 1.94)|0|intersection' "$DUMP" 52 | grep -F 'POINT(1.16 1.96)|0|intersection' "$DUMP" 53 | 54 | #----------------------------------------------------------------------------- 55 | -------------------------------------------------------------------------------- /test/t/usage-and-help.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # osmcoastline should print usage and help 5 | # 6 | #----------------------------------------------------------------------------- 7 | 8 | # shellcheck source=test/init.sh 9 | . "$1/test/init.sh" 10 | 11 | set -x 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | "$OSMC" >"$LOG" 2>&1 16 | RC=$? 17 | set -e 18 | 19 | test $RC -eq 4 20 | 21 | grep '^Usage: osmcoastline .OPTIONS. OSMFILE$' "$LOG" 22 | 23 | "$OSMC" -h >"$LOG" 2>&1 24 | 25 | grep '^Usage: osmcoastline .OPTIONS. OSMFILE$' "$LOG" 26 | 27 | "$OSMC" --help >"$LOG" 2>&1 28 | 29 | grep '^Usage: osmcoastline .OPTIONS. OSMFILE$' "$LOG" 30 | 31 | #----------------------------------------------------------------------------- 32 | -------------------------------------------------------------------------------- /test/t/valid-antimeridian.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # Valid island crossing the antimeridian. 5 | # 6 | #----------------------------------------------------------------------------- 7 | 8 | # shellcheck source=test/init.sh 9 | . "$1/test/init.sh" 10 | 11 | set -x 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | cat <<'OSM' >"$INPUT" 16 | n100 v1 x-180.0 y1.1 17 | n101 v1 x-179.0 y1.1 18 | n102 v1 x-179.0 y1.4 19 | n103 v1 x-180.0 y1.4 20 | n110 v1 x180.0 y1.4 21 | n111 v1 x179.0 y1.4 22 | n112 v1 x179.0 y1.1 23 | n113 v1 x180.0 y1.1 24 | w200 v1 Tnatural=coastline Nn100,n101,n102,n103,n100 25 | w201 v1 Tnatural=coastline Nn110,n111,n112,n113,n110 26 | OSM 27 | 28 | #----------------------------------------------------------------------------- 29 | 30 | set -e 31 | 32 | "$OSMC" --verbose --overwrite --output-lines --srs="$SRID" --output-database="$DB" "$INPUT" >"$LOG" 2>&1 33 | 34 | test $? -eq 0 35 | 36 | grep 'Turned 0 polygons around.$' "$LOG" 37 | 38 | grep '^There were 0 warnings.$' "$LOG" 39 | grep '^There were 0 errors.$' "$LOG" 40 | 41 | check_count land_polygons 2; 42 | check_count error_points 0; 43 | check_count error_lines 0; 44 | 45 | echo "SELECT InsertEpsgSrid(4326);" | $SQL 46 | 47 | echo "SELECT AsText(Transform(geometry, 4326)) FROM land_polygons;" | $SQL >"$DUMP" 48 | 49 | grep -F 'POLYGON((-180 1.1, -180 1.4, -179 1.4, -179 1.1, -180 1.1))' "$DUMP" 50 | grep -F 'POLYGON((180 1.4, 180 1.1, 179 1.1, 179 1.4, 180 1.4))' "$DUMP" 51 | 52 | echo "SELECT AsText(Transform(geometry, 4326)) FROM lines;" | $SQL >"$DUMP" 53 | 54 | grep -F 'LINESTRING(-180 1.4, -179 1.4, -179 1.1, -180 1.1)' "$DUMP" 55 | grep -F 'LINESTRING(180 1.1, 179 1.1, 179 1.4, 180 1.4)' "$DUMP" 56 | 57 | #----------------------------------------------------------------------------- 58 | -------------------------------------------------------------------------------- /test/t/valid-inland-sea-with-island.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # Valid coastline with "inland sea" and island in that. 5 | # 6 | #----------------------------------------------------------------------------- 7 | 8 | # shellcheck source=test/init.sh 9 | . "$1/test/init.sh" 10 | 11 | set -x 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | "$BIN_DIR/src/nodegrid2opl" << 'NODES' >"$INPUT" 16 | 17 | 0--------1----\ 18 | / \--2\ 19 | 3 4------5 6 20 | | | bc | | 21 | | | d | | 22 | \ \7---8/ / 23 | \ / 24 | -9------------a 25 | 26 | NODES 27 | 28 | cat <<'OSM' >>"$INPUT" 29 | w200 v1 Tnatural=coastline Nn100,n103,n109,n110,n106,n102,n101,n100 30 | w201 v1 Tnatural=coastline Nn104,n105,n108,n107,n104 31 | w202 v1 Tnatural=coastline Nn111,n112,n113,n111 32 | OSM 33 | 34 | #----------------------------------------------------------------------------- 35 | 36 | "$OSMC" --verbose --overwrite --srs="$SRID" --output-database="$DB" "$INPUT" >"$LOG" 2>&1 37 | RC=$? 38 | set -e 39 | 40 | test $RC -eq 2 41 | 42 | if [ "$SRID" = "4326" ]; then 43 | grep 'Found 3 rings in input data.$' "$LOG" 44 | grep '^There were 3 warnings.$' "$LOG" 45 | check_count error_lines 3; 46 | else 47 | # "questionables" are not checked in 3857 48 | grep '^There were 0 warnings.$' "$LOG" 49 | check_count error_lines 0; 50 | fi 51 | 52 | grep '^There were 1 errors.$' "$LOG" 53 | 54 | check_count land_polygons 0; 55 | check_count error_points 0; 56 | 57 | #----------------------------------------------------------------------------- 58 | -------------------------------------------------------------------------------- /test/t/valid-inland-sea.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # Valid coastline with "inland sea". 5 | # 6 | #----------------------------------------------------------------------------- 7 | 8 | # shellcheck source=test/init.sh 9 | . "$1/test/init.sh" 10 | 11 | set -x 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | "$BIN_DIR/src/nodegrid2opl" << 'NODES' >"$INPUT" 16 | 17 | 0--------1----\ 18 | / \--2\ 19 | 3 4------5 6 20 | \ \7--8/ / 21 | \ / 22 | -9------------a 23 | 24 | NODES 25 | 26 | cat <<'OSM' >>"$INPUT" 27 | w200 v1 Tnatural=coastline Nn100,n103,n109,n110,n106,n102,n101,n100 28 | w201 v1 Tnatural=coastline Nn104,n105,n108,n107,n104 29 | OSM 30 | 31 | #----------------------------------------------------------------------------- 32 | 33 | "$OSMC" --verbose --overwrite --srs="$SRID" --output-database="$DB" "$INPUT" >"$LOG" 2>&1 34 | RC=$? 35 | set -e 36 | 37 | if [ "$SRID" = "4326" ]; then 38 | test $RC -eq 1 39 | grep '^There were 1 warnings.$' "$LOG" 40 | check_count error_lines 1; 41 | else 42 | # "questionables" are not checked in 3857 43 | test $RC -eq 0 44 | grep '^There were 0 warnings.$' "$LOG" 45 | check_count error_lines 0; 46 | fi 47 | 48 | grep '^There were 0 errors.$' "$LOG" 49 | 50 | check_count land_polygons 1; 51 | check_count error_points 0; 52 | 53 | echo "SELECT InsertEpsgSrid(4326);" | $SQL 54 | 55 | echo "SELECT AsText(Transform(geometry, 4326)) FROM land_polygons;" | $SQL \ 56 | | grep -F 'POLYGON((1.05 1.99, 1.14 1.99, 1.23 1.98, 1.25 1.97, 1.21 1.94, 1.08 1.94, 1.04 1.97, 1.05 1.99), (1.1 1.97, 1.12 1.96, 1.15 1.96, 1.17 1.97, 1.1 1.97))' 57 | 58 | if [ "$SRID" = "4326" ]; then 59 | echo "SELECT AsText(geometry), osm_id, error FROM error_lines;" | $SQL \ 60 | | grep -F 'LINESTRING(1.1 1.97, 1.17 1.97, 1.15 1.96, 1.12 1.96, 1.1 1.97)|201|questionable' 61 | fi 62 | 63 | #----------------------------------------------------------------------------- 64 | -------------------------------------------------------------------------------- /test/t/valid-island-from-one-way.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # Valid small "island" with coastline all around. 5 | # 6 | #----------------------------------------------------------------------------- 7 | 8 | # shellcheck source=test/init.sh 9 | . "$1/test/init.sh" 10 | 11 | set -x 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | cat <<'OSM' >"$INPUT" 16 | n100 v1 x80.01 y10.01 17 | n101 v1 x80.04 y10.01 18 | n102 v1 x80.04 y10.04 19 | n103 v1 x80.01 y10.04 20 | w200 v1 Tnatural=coastline Nn100,n101,n102,n103,n100 21 | OSM 22 | 23 | #----------------------------------------------------------------------------- 24 | 25 | set -e 26 | 27 | "$OSMC" --verbose --overwrite --srs="$SRID" --output-database="$DB" "$INPUT" >"$LOG" 2>&1 28 | 29 | test $? -eq 0 30 | 31 | grep 'Turned 0 polygons around.$' "$LOG" 32 | 33 | grep '^There were 0 warnings.$' "$LOG" 34 | grep '^There were 0 errors.$' "$LOG" 35 | 36 | check_count land_polygons 1; 37 | check_count error_points 0; 38 | check_count error_lines 0; 39 | 40 | echo "SELECT InsertEpsgSrid(4326);" | $SQL 41 | 42 | echo "SELECT AsText(Transform(geometry, 4326)) FROM land_polygons;" | $SQL \ 43 | | grep -F 'POLYGON((80.01 10.01, 80.01 10.04, 80.04 10.04, 80.04 10.01, 80.01 10.01))' 44 | 45 | #----------------------------------------------------------------------------- 46 | -------------------------------------------------------------------------------- /test/t/valid-island-from-two-ways.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # Valid small "island" made up from two ways with coastline all around. 5 | # 6 | #----------------------------------------------------------------------------- 7 | 8 | # shellcheck source=test/init.sh 9 | . "$1/test/init.sh" 10 | 11 | set -x 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | cat <<'OSM' >"$INPUT" 16 | n100 v1 x1.01 y1.01 17 | n101 v1 x1.02 y1.01 18 | n102 v1 x1.03 y1.02 19 | n103 v1 x1.04 y1.02 20 | n104 v1 x1.05 y1.03 21 | n105 v1 x1.01 y1.03 22 | w200 v1 Tnatural=coastline Nn100,n101,n102 23 | w201 v1 Tnatural=coastline Nn102,n103,n104,n105,n100 24 | OSM 25 | 26 | #----------------------------------------------------------------------------- 27 | 28 | set -e 29 | 30 | "$OSMC" --verbose --overwrite --srs="$SRID" --output-database="$DB" "$INPUT" >"$LOG" 2>&1 31 | 32 | test $? -eq 0 33 | 34 | grep 'There are 1 coastline rings (0 from a single closed way and 1 others).$' "$LOG" 35 | 36 | grep '^There were 0 warnings.$' "$LOG" 37 | grep '^There were 0 errors.$' "$LOG" 38 | 39 | check_count land_polygons 1; 40 | check_count error_points 0; 41 | check_count error_lines 0; 42 | 43 | echo "SELECT InsertEpsgSrid(4326);" | $SQL 44 | 45 | echo "SELECT AsText(Transform(geometry, 4326)) FROM land_polygons;" | $SQL \ 46 | | grep -F 'POLYGON((1.01 1.01, 1.01 1.03, 1.05 1.03, 1.04 1.02, 1.03 1.02, 1.02 1.01, 1.01 1.01))' 47 | 48 | #----------------------------------------------------------------------------- 49 | -------------------------------------------------------------------------------- /test/t/valid-two-small-islands.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #----------------------------------------------------------------------------- 3 | # 4 | # Two valid small "islands" with coastline all around. 5 | # 6 | #----------------------------------------------------------------------------- 7 | 8 | # shellcheck source=test/init.sh 9 | . "$1/test/init.sh" 10 | 11 | set -x 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | cat <<'OSM' >"$INPUT" 16 | n100 v1 x1.01 y1.01 17 | n101 v1 x1.04 y1.01 18 | n102 v1 x1.04 y1.04 19 | n103 v1 x1.01 y1.04 20 | n110 v1 x1.01 y1.11 21 | n111 v1 x1.04 y1.11 22 | n112 v1 x1.04 y1.14 23 | n113 v1 x1.01 y1.14 24 | w200 v1 Tnatural=coastline Nn100,n101,n102,n103,n100 25 | w201 v1 Tnatural=coastline Nn110,n111,n112,n113,n110 26 | OSM 27 | 28 | #----------------------------------------------------------------------------- 29 | 30 | set -e 31 | 32 | "$OSMC" --verbose --overwrite --srs="$SRID" --output-database="$DB" "$INPUT" >"$LOG" 2>&1 33 | 34 | test $? -eq 0 35 | 36 | grep 'Turned 0 polygons around.$' "$LOG" 37 | 38 | grep '^There were 0 warnings.$' "$LOG" 39 | grep '^There were 0 errors.$' "$LOG" 40 | 41 | check_count land_polygons 2; 42 | check_count error_points 0; 43 | check_count error_lines 0; 44 | 45 | echo "SELECT InsertEpsgSrid(4326);" | $SQL 46 | 47 | echo "SELECT AsText(Transform(geometry, 4326)) FROM land_polygons;" | $SQL >"$DUMP" 48 | grep -F 'POLYGON((1.01 1.01, 1.01 1.04, 1.04 1.04, 1.04 1.01, 1.01 1.01))' "$DUMP" 49 | grep -F 'POLYGON((1.01 1.11, 1.01 1.14, 1.04 1.14, 1.04 1.11, 1.01 1.11))' "$DUMP" 50 | 51 | #----------------------------------------------------------------------------- 52 | --------------------------------------------------------------------------------