mirror of
https://gitlab.com/Trygve/contour-creator.git
synced 2026-03-14 16:04:04 +00:00
Compare commits
41 Commits
d046e18c43
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b3936ae649 | |||
| 08dce8eb7e | |||
| e739821264 | |||
| 59ce1c0177 | |||
| 6e3217105e | |||
| 093f55f525 | |||
| 903f2e740f | |||
|
|
16279bb224 | ||
| 3bf8dbfb79 | |||
| dc020a4f38 | |||
| f857f7e951 | |||
| 264c0341ed | |||
| b4b89c2282 | |||
| 9ab5aac40f | |||
| 7037874923 | |||
| a785407c6d | |||
| 97c52ecef3 | |||
| 9300fa729c | |||
|
|
c47565eabb | ||
| 62d62987d3 | |||
| 5845ae6693 | |||
| 47d0e29868 | |||
| 43f8b78830 | |||
| 08255875c1 | |||
| ec0a31db5d | |||
|
|
32a0b18f8e | ||
|
|
ab96f9477e | ||
| 790cf44f8b | |||
| df7cac1095 | |||
| 93fcbc6a39 | |||
| 95649f005b | |||
| 1ec3e8c778 | |||
| c535eec958 | |||
| 4f709d3b07 | |||
| 4cd4f773bf | |||
| 9d8df3256d | |||
|
|
77467594bd | ||
| 0188b9e29d | |||
| b800ceaf98 | |||
| 1dcfb0e737 | |||
|
|
228945a767 |
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "extern/argh"]
|
||||||
|
path = extern/argh
|
||||||
|
url = https://github.com/adishavit/argh.git
|
||||||
@@ -1,15 +1,22 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
|
||||||
project(
|
project(
|
||||||
contour-creator
|
contour-creator
|
||||||
LANGUAGES CXX)
|
LANGUAGES CXX)
|
||||||
|
|
||||||
find_package(GDAL CONFIG REQUIRED)
|
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME}
|
add_executable(${PROJECT_NAME}
|
||||||
src/HeightMap.cpp src/CellMap.cpp src/main.cpp
|
src/HeightMap.cpp src/main.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Argh is a simple argrument parser
|
||||||
|
add_subdirectory(extern/argh)
|
||||||
|
target_link_libraries(${PROJECT_NAME} PRIVATE argh)
|
||||||
|
|
||||||
|
# Gdal is used for geodata IO
|
||||||
|
find_package(GDAL CONFIG REQUIRED)
|
||||||
|
target_link_libraries(${PROJECT_NAME} PRIVATE GDAL::GDAL)
|
||||||
|
|
||||||
find_package(OpenMP)
|
find_package(OpenMP)
|
||||||
if(OpenMP_CXX_FOUND)
|
if(OpenMP_CXX_FOUND)
|
||||||
target_link_libraries(${PROJECT_NAME} PUBLIC OpenMP::OpenMP_CXX)
|
target_link_libraries(${PROJECT_NAME} PUBLIC OpenMP::OpenMP_CXX)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(${PROJECT_NAME} GDAL::GDAL)
|
|
||||||
11
README.md
11
README.md
@@ -4,13 +4,17 @@ This is our project for the INF205: Resource-efficient programming course
|
|||||||
## Status
|
## Status
|
||||||
- [x] Read .tif file into memory using gdal
|
- [x] Read .tif file into memory using gdal
|
||||||
- [x] Run the marching squares algorithm and produce a "cellmap"
|
- [x] Run the marching squares algorithm and produce a "cellmap"
|
||||||
- [ ] Use a lookuptable to produce a vector file from the "cellmap"
|
- [x] Use a lookuptable to produce a vector file from the "cellmap"
|
||||||
## Dependencies
|
## Dependencies
|
||||||
- GDAL >= 3.5
|
- GDAL >= 3.5
|
||||||
- OpenMP
|
- OpenMP
|
||||||
- CMake
|
- CMake
|
||||||
|
|
||||||
If you want to clone the repo with the example files you need [git-lfs](https://git-lfs.com/) installed and activated with ´git lfs install´
|
If you want to clone the repo with the example files you need [git-lfs](https://git-lfs.com/) installed and activated with ´git lfs install´
|
||||||
|
You also need to clone with submodules to get the argh library:
|
||||||
|
```
|
||||||
|
git clone --recurse-submodules https://gitlab.com/Trygve/contour-creator
|
||||||
|
```
|
||||||
To install the packages on Fedora run
|
To install the packages on Fedora run
|
||||||
```
|
```
|
||||||
dnf install g++ git-lfs cmake gdal-devel
|
dnf install g++ git-lfs cmake gdal-devel
|
||||||
@@ -27,8 +31,7 @@ cmake -DCMAKE_BUILD_TYPE=Debug ..
|
|||||||
cmake --build . --parallel
|
cmake --build . --parallel
|
||||||
```
|
```
|
||||||
Then you can run `./contour-creator PATH/TO/HEIGTHMAP.TIF`
|
Then you can run `./contour-creator PATH/TO/HEIGTHMAP.TIF`
|
||||||
Run an example file: `./contour-creator "../example_files/Follo 2014-dtm.tif"`
|
Run an example file: `./contour-creator "../example_files/ås.tif"`
|
||||||
Currently the program outputs to ´out.shp´ in the current directory.
|
|
||||||
|
|
||||||
## Docs
|
## Docs
|
||||||
[Doxygen output](https://trygve.gitlab.io/contour-creator/)
|
[Doxygen output](https://trygve.gitlab.io/contour-creator/)
|
||||||
@@ -3,4 +3,4 @@ rm -rf build
|
|||||||
mkdir build
|
mkdir build
|
||||||
cmake -DCMAKE_BUILD_TYPE=Debug -B build
|
cmake -DCMAKE_BUILD_TYPE=Debug -B build
|
||||||
cmake --build build --parallel
|
cmake --build build --parallel
|
||||||
build/contour-creator 'example_files/crop.tif'
|
build/contour-creator --interval=1 --blur --stats example_files/ås_crop.tif
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<gaphor xmlns="http://gaphor.sourceforge.net/model" version="3.0" gaphor-version="2.25.0">
|
<gaphor xmlns="http://gaphor.sourceforge.net/model" version="3.0" gaphor-version="2.25.1">
|
||||||
<StyleSheet id="58d6989a-66f8-11ec-b4c8-0456e5e540ed"/>
|
<StyleSheet id="58d6989a-66f8-11ec-b4c8-0456e5e540ed"/>
|
||||||
<Package id="58d6c2e8-66f8-11ec-b4c8-0456e5e540ed">
|
<Package id="58d6c2e8-66f8-11ec-b4c8-0456e5e540ed">
|
||||||
<name>
|
<name>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<ownedType>
|
<ownedType>
|
||||||
<reflist>
|
<reflist>
|
||||||
<ref refid="0c22379b-e76f-11ee-8193-8bb162b1502a"/>
|
<ref refid="0c22379b-e76f-11ee-8193-8bb162b1502a"/>
|
||||||
<ref refid="4a8d98db-e76f-11ee-9ded-8bb162b1502a"/>
|
<ref refid="1754a50e-0cac-11ef-86ae-59b56c446183"/>
|
||||||
</reflist>
|
</reflist>
|
||||||
</ownedType>
|
</ownedType>
|
||||||
</Package>
|
</Package>
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<ownedPresentation>
|
<ownedPresentation>
|
||||||
<reflist>
|
<reflist>
|
||||||
<ref refid="0c228e70-e76f-11ee-90ec-8bb162b1502a"/>
|
<ref refid="0c228e70-e76f-11ee-90ec-8bb162b1502a"/>
|
||||||
<ref refid="4a8ddfb4-e76f-11ee-b091-8bb162b1502a"/>
|
<ref refid="1754dd05-0cac-11ef-824e-59b56c446183"/>
|
||||||
</reflist>
|
</reflist>
|
||||||
</ownedPresentation>
|
</ownedPresentation>
|
||||||
</Diagram>
|
</Diagram>
|
||||||
@@ -47,6 +47,8 @@
|
|||||||
<ref refid="f72f1442-f674-11ee-b3f4-756c0364fd19"/>
|
<ref refid="f72f1442-f674-11ee-b3f4-756c0364fd19"/>
|
||||||
<ref refid="033b1fcf-f675-11ee-8fa9-756c0364fd19"/>
|
<ref refid="033b1fcf-f675-11ee-8fa9-756c0364fd19"/>
|
||||||
<ref refid="79fcf06c-0171-11ef-b98e-75fbb1f7e3ee"/>
|
<ref refid="79fcf06c-0171-11ef-b98e-75fbb1f7e3ee"/>
|
||||||
|
<ref refid="56cb7284-0cac-11ef-8675-59b56c446183"/>
|
||||||
|
<ref refid="77a8bf26-0cac-11ef-9642-59b56c446183"/>
|
||||||
</reflist>
|
</reflist>
|
||||||
</ownedAttribute>
|
</ownedAttribute>
|
||||||
<package>
|
<package>
|
||||||
@@ -69,7 +71,7 @@
|
|||||||
<val>311.0</val>
|
<val>311.0</val>
|
||||||
</width>
|
</width>
|
||||||
<height>
|
<height>
|
||||||
<val>151.0</val>
|
<val>185.0</val>
|
||||||
</height>
|
</height>
|
||||||
<diagram>
|
<diagram>
|
||||||
<ref refid="58d6c536-66f8-11ec-b4c8-0456e5e540ed"/>
|
<ref refid="58d6c536-66f8-11ec-b4c8-0456e5e540ed"/>
|
||||||
@@ -111,80 +113,6 @@
|
|||||||
<val></val>
|
<val></val>
|
||||||
</name>
|
</name>
|
||||||
</Property>
|
</Property>
|
||||||
<Class id="4a8d98db-e76f-11ee-9ded-8bb162b1502a">
|
|
||||||
<name>
|
|
||||||
<val>Cell</val>
|
|
||||||
</name>
|
|
||||||
<ownedAttribute>
|
|
||||||
<reflist>
|
|
||||||
<ref refid="58a782c4-e76f-11ee-af59-8bb162b1502a"/>
|
|
||||||
<ref refid="59eb8e6d-e76f-11ee-9d79-8bb162b1502a"/>
|
|
||||||
<ref refid="625d120c-e76f-11ee-aeac-8bb162b1502a"/>
|
|
||||||
<ref refid="8f3e5d14-0171-11ef-91b0-75fbb1f7e3ee"/>
|
|
||||||
</reflist>
|
|
||||||
</ownedAttribute>
|
|
||||||
<package>
|
|
||||||
<ref refid="58d6c2e8-66f8-11ec-b4c8-0456e5e540ed"/>
|
|
||||||
</package>
|
|
||||||
<presentation>
|
|
||||||
<reflist>
|
|
||||||
<ref refid="4a8ddfb4-e76f-11ee-b091-8bb162b1502a"/>
|
|
||||||
</reflist>
|
|
||||||
</presentation>
|
|
||||||
</Class>
|
|
||||||
<ClassItem id="4a8ddfb4-e76f-11ee-b091-8bb162b1502a">
|
|
||||||
<matrix>
|
|
||||||
<val>(1.0, 0.0, 0.0, 1.0, 567.9247472572557, 154.04296875)</val>
|
|
||||||
</matrix>
|
|
||||||
<top-left>
|
|
||||||
<val>(0.0, 0.0)</val>
|
|
||||||
</top-left>
|
|
||||||
<width>
|
|
||||||
<val>311.0</val>
|
|
||||||
</width>
|
|
||||||
<height>
|
|
||||||
<val>117.0</val>
|
|
||||||
</height>
|
|
||||||
<diagram>
|
|
||||||
<ref refid="58d6c536-66f8-11ec-b4c8-0456e5e540ed"/>
|
|
||||||
</diagram>
|
|
||||||
<show_operations>
|
|
||||||
<val>0</val>
|
|
||||||
</show_operations>
|
|
||||||
<subject>
|
|
||||||
<ref refid="4a8d98db-e76f-11ee-9ded-8bb162b1502a"/>
|
|
||||||
</subject>
|
|
||||||
</ClassItem>
|
|
||||||
<Property id="58a782c4-e76f-11ee-af59-8bb162b1502a">
|
|
||||||
<class_>
|
|
||||||
<ref refid="4a8d98db-e76f-11ee-9ded-8bb162b1502a"/>
|
|
||||||
</class_>
|
|
||||||
<name>
|
|
||||||
<val>width</val>
|
|
||||||
</name>
|
|
||||||
<typeValue>
|
|
||||||
<val>int</val>
|
|
||||||
</typeValue>
|
|
||||||
</Property>
|
|
||||||
<Property id="59eb8e6d-e76f-11ee-9d79-8bb162b1502a">
|
|
||||||
<class_>
|
|
||||||
<ref refid="4a8d98db-e76f-11ee-9ded-8bb162b1502a"/>
|
|
||||||
</class_>
|
|
||||||
<name>
|
|
||||||
<val>height</val>
|
|
||||||
</name>
|
|
||||||
<typeValue>
|
|
||||||
<val>int</val>
|
|
||||||
</typeValue>
|
|
||||||
</Property>
|
|
||||||
<Property id="625d120c-e76f-11ee-aeac-8bb162b1502a">
|
|
||||||
<class_>
|
|
||||||
<ref refid="4a8d98db-e76f-11ee-9ded-8bb162b1502a"/>
|
|
||||||
</class_>
|
|
||||||
<name>
|
|
||||||
<val>+ cells: int*</val>
|
|
||||||
</name>
|
|
||||||
</Property>
|
|
||||||
<Property id="f1a417ac-f674-11ee-8f46-756c0364fd19">
|
<Property id="f1a417ac-f674-11ee-8f46-756c0364fd19">
|
||||||
<class_>
|
<class_>
|
||||||
<ref refid="0c22379b-e76f-11ee-8193-8bb162b1502a"/>
|
<ref refid="0c22379b-e76f-11ee-8193-8bb162b1502a"/>
|
||||||
@@ -223,15 +151,116 @@
|
|||||||
<val>reference_system</val>
|
<val>reference_system</val>
|
||||||
</typeValue>
|
</typeValue>
|
||||||
</Property>
|
</Property>
|
||||||
<Property id="8f3e5d14-0171-11ef-91b0-75fbb1f7e3ee">
|
<Diagram id="14a35e81-0cac-11ef-8c1c-59b56c446183">
|
||||||
|
<diagramType>
|
||||||
|
<val>cls</val>
|
||||||
|
</diagramType>
|
||||||
|
<name>
|
||||||
|
<val>New Diagram</val>
|
||||||
|
</name>
|
||||||
|
</Diagram>
|
||||||
|
<Class id="1754a50e-0cac-11ef-86ae-59b56c446183">
|
||||||
|
<name>
|
||||||
|
<val>Point</val>
|
||||||
|
</name>
|
||||||
|
<ownedAttribute>
|
||||||
|
<reflist>
|
||||||
|
<ref refid="34feac8d-0cac-11ef-85b7-59b56c446183"/>
|
||||||
|
<ref refid="3768ce02-0cac-11ef-9b57-59b56c446183"/>
|
||||||
|
<ref refid="3eeba789-0cac-11ef-b42f-59b56c446183"/>
|
||||||
|
<ref refid="46c075e3-0cac-11ef-8e54-59b56c446183"/>
|
||||||
|
</reflist>
|
||||||
|
</ownedAttribute>
|
||||||
|
<package>
|
||||||
|
<ref refid="58d6c2e8-66f8-11ec-b4c8-0456e5e540ed"/>
|
||||||
|
</package>
|
||||||
|
<presentation>
|
||||||
|
<reflist>
|
||||||
|
<ref refid="1754dd05-0cac-11ef-824e-59b56c446183"/>
|
||||||
|
</reflist>
|
||||||
|
</presentation>
|
||||||
|
</Class>
|
||||||
|
<ClassItem id="1754dd05-0cac-11ef-824e-59b56c446183">
|
||||||
|
<matrix>
|
||||||
|
<val>(1.0, 0.0, 0.0, 1.0, 584.7889709472656, 160.69140625)</val>
|
||||||
|
</matrix>
|
||||||
|
<top-left>
|
||||||
|
<val>(0.0, 0.0)</val>
|
||||||
|
</top-left>
|
||||||
|
<width>
|
||||||
|
<val>130.0</val>
|
||||||
|
</width>
|
||||||
|
<height>
|
||||||
|
<val>125.0</val>
|
||||||
|
</height>
|
||||||
|
<diagram>
|
||||||
|
<ref refid="58d6c536-66f8-11ec-b4c8-0456e5e540ed"/>
|
||||||
|
</diagram>
|
||||||
|
<show_operations>
|
||||||
|
<val>0</val>
|
||||||
|
</show_operations>
|
||||||
|
<subject>
|
||||||
|
<ref refid="1754a50e-0cac-11ef-86ae-59b56c446183"/>
|
||||||
|
</subject>
|
||||||
|
</ClassItem>
|
||||||
|
<Property id="34feac8d-0cac-11ef-85b7-59b56c446183">
|
||||||
<class_>
|
<class_>
|
||||||
<ref refid="4a8d98db-e76f-11ee-9ded-8bb162b1502a"/>
|
<ref refid="1754a50e-0cac-11ef-86ae-59b56c446183"/>
|
||||||
</class_>
|
</class_>
|
||||||
<name>
|
<name>
|
||||||
<val>OGRSpatialReference</val>
|
<val>x</val>
|
||||||
</name>
|
</name>
|
||||||
<typeValue>
|
<typeValue>
|
||||||
<val>reference_system</val>
|
<val>int</val>
|
||||||
</typeValue>
|
</typeValue>
|
||||||
</Property>
|
</Property>
|
||||||
|
<Property id="3768ce02-0cac-11ef-9b57-59b56c446183">
|
||||||
|
<class_>
|
||||||
|
<ref refid="1754a50e-0cac-11ef-86ae-59b56c446183"/>
|
||||||
|
</class_>
|
||||||
|
<name>
|
||||||
|
<val>y</val>
|
||||||
|
</name>
|
||||||
|
<typeValue>
|
||||||
|
<val>int</val>
|
||||||
|
</typeValue>
|
||||||
|
</Property>
|
||||||
|
<Property id="3eeba789-0cac-11ef-b42f-59b56c446183">
|
||||||
|
<class_>
|
||||||
|
<ref refid="1754a50e-0cac-11ef-86ae-59b56c446183"/>
|
||||||
|
</class_>
|
||||||
|
<name>
|
||||||
|
<val>mscase</val>
|
||||||
|
</name>
|
||||||
|
<typeValue>
|
||||||
|
<val>int</val>
|
||||||
|
</typeValue>
|
||||||
|
</Property>
|
||||||
|
<Property id="46c075e3-0cac-11ef-8e54-59b56c446183">
|
||||||
|
<class_>
|
||||||
|
<ref refid="1754a50e-0cac-11ef-86ae-59b56c446183"/>
|
||||||
|
</class_>
|
||||||
|
<name>
|
||||||
|
<val>allocated</val>
|
||||||
|
</name>
|
||||||
|
<typeValue>
|
||||||
|
<val>bool</val>
|
||||||
|
</typeValue>
|
||||||
|
</Property>
|
||||||
|
<Property id="56cb7284-0cac-11ef-8675-59b56c446183">
|
||||||
|
<class_>
|
||||||
|
<ref refid="0c22379b-e76f-11ee-8193-8bb162b1502a"/>
|
||||||
|
</class_>
|
||||||
|
<name>
|
||||||
|
<val>geotransform: double*</val>
|
||||||
|
</name>
|
||||||
|
</Property>
|
||||||
|
<Property id="77a8bf26-0cac-11ef-9642-59b56c446183">
|
||||||
|
<class_>
|
||||||
|
<ref refid="0c22379b-e76f-11ee-8193-8bb162b1502a"/>
|
||||||
|
</class_>
|
||||||
|
<name>
|
||||||
|
<val>filepath: char*</val>
|
||||||
|
</name>
|
||||||
|
</Property>
|
||||||
</gaphor>
|
</gaphor>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="684" height="170" viewBox="0 0 684 170">
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="520" height="204" viewBox="0 0 520 204">
|
||||||
<defs>
|
<defs>
|
||||||
<g>
|
<g>
|
||||||
<g id="glyph-0-0">
|
<g id="glyph-0-0">
|
||||||
@@ -30,10 +30,13 @@
|
|||||||
<path d="M 3.625 -1.109375 L 3.625 2.90625 L 1.171875 2.90625 L 1.171875 -7.65625 L 3.625 -7.65625 L 3.625 -6.53125 C 3.957031 -6.976562 4.328125 -7.304688 4.734375 -7.515625 C 5.148438 -7.734375 5.625 -7.84375 6.15625 -7.84375 C 7.101562 -7.84375 7.878906 -7.46875 8.484375 -6.71875 C 9.085938 -5.96875 9.390625 -5.003906 9.390625 -3.828125 C 9.390625 -2.640625 9.085938 -1.671875 8.484375 -0.921875 C 7.878906 -0.171875 7.101562 0.203125 6.15625 0.203125 C 5.625 0.203125 5.148438 0.0976562 4.734375 -0.109375 C 4.328125 -0.328125 3.957031 -0.660156 3.625 -1.109375 Z M 5.25 -6.0625 C 4.726562 -6.0625 4.328125 -5.867188 4.046875 -5.484375 C 3.765625 -5.097656 3.625 -4.546875 3.625 -3.828125 C 3.625 -3.097656 3.765625 -2.539062 4.046875 -2.15625 C 4.328125 -1.769531 4.726562 -1.578125 5.25 -1.578125 C 5.769531 -1.578125 6.164062 -1.769531 6.4375 -2.15625 C 6.71875 -2.539062 6.859375 -3.097656 6.859375 -3.828125 C 6.859375 -4.554688 6.71875 -5.109375 6.4375 -5.484375 C 6.164062 -5.867188 5.769531 -6.0625 5.25 -6.0625 Z M 5.25 -6.0625 "/>
|
<path d="M 3.625 -1.109375 L 3.625 2.90625 L 1.171875 2.90625 L 1.171875 -7.65625 L 3.625 -7.65625 L 3.625 -6.53125 C 3.957031 -6.976562 4.328125 -7.304688 4.734375 -7.515625 C 5.148438 -7.734375 5.625 -7.84375 6.15625 -7.84375 C 7.101562 -7.84375 7.878906 -7.46875 8.484375 -6.71875 C 9.085938 -5.96875 9.390625 -5.003906 9.390625 -3.828125 C 9.390625 -2.640625 9.085938 -1.671875 8.484375 -0.921875 C 7.878906 -0.171875 7.101562 0.203125 6.15625 0.203125 C 5.625 0.203125 5.148438 0.0976562 4.734375 -0.109375 C 4.328125 -0.328125 3.957031 -0.660156 3.625 -1.109375 Z M 5.25 -6.0625 C 4.726562 -6.0625 4.328125 -5.867188 4.046875 -5.484375 C 3.765625 -5.097656 3.625 -4.546875 3.625 -3.828125 C 3.625 -3.097656 3.765625 -2.539062 4.046875 -2.15625 C 4.328125 -1.769531 4.726562 -1.578125 5.25 -1.578125 C 5.769531 -1.578125 6.164062 -1.769531 6.4375 -2.15625 C 6.71875 -2.539062 6.859375 -3.097656 6.859375 -3.828125 C 6.859375 -4.554688 6.71875 -5.109375 6.4375 -5.484375 C 6.164062 -5.867188 5.769531 -6.0625 5.25 -6.0625 Z M 5.25 -6.0625 "/>
|
||||||
</g>
|
</g>
|
||||||
<g id="glyph-0-9">
|
<g id="glyph-0-9">
|
||||||
<path d="M 9.375 -0.5625 C 8.894531 -0.3125 8.390625 -0.125 7.859375 0 C 7.335938 0.132812 6.796875 0.203125 6.234375 0.203125 C 4.523438 0.203125 3.175781 -0.269531 2.1875 -1.21875 C 1.195312 -2.175781 0.703125 -3.46875 0.703125 -5.09375 C 0.703125 -6.726562 1.195312 -8.019531 2.1875 -8.96875 C 3.175781 -9.914062 4.523438 -10.390625 6.234375 -10.390625 C 6.796875 -10.390625 7.335938 -10.328125 7.859375 -10.203125 C 8.390625 -10.078125 8.894531 -9.882812 9.375 -9.625 L 9.375 -7.515625 C 8.882812 -7.847656 8.398438 -8.09375 7.921875 -8.25 C 7.453125 -8.40625 6.957031 -8.484375 6.4375 -8.484375 C 5.5 -8.484375 4.757812 -8.179688 4.21875 -7.578125 C 3.6875 -6.972656 3.421875 -6.144531 3.421875 -5.09375 C 3.421875 -4.039062 3.6875 -3.210938 4.21875 -2.609375 C 4.757812 -2.003906 5.5 -1.703125 6.4375 -1.703125 C 6.957031 -1.703125 7.453125 -1.78125 7.921875 -1.9375 C 8.398438 -2.09375 8.882812 -2.335938 9.375 -2.671875 Z M 9.375 -0.5625 "/>
|
<path d="M 1.28125 -10.203125 L 5.65625 -10.203125 C 6.957031 -10.203125 7.953125 -9.914062 8.640625 -9.34375 C 9.335938 -8.769531 9.6875 -7.945312 9.6875 -6.875 C 9.6875 -5.800781 9.335938 -4.976562 8.640625 -4.40625 C 7.953125 -3.832031 6.957031 -3.546875 5.65625 -3.546875 L 3.921875 -3.546875 L 3.921875 0 L 1.28125 0 Z M 3.921875 -8.296875 L 3.921875 -5.453125 L 5.375 -5.453125 C 5.882812 -5.453125 6.273438 -5.570312 6.546875 -5.8125 C 6.828125 -6.0625 6.96875 -6.414062 6.96875 -6.875 C 6.96875 -7.332031 6.828125 -7.679688 6.546875 -7.921875 C 6.273438 -8.171875 5.882812 -8.296875 5.375 -8.296875 Z M 3.921875 -8.296875 "/>
|
||||||
</g>
|
</g>
|
||||||
<g id="glyph-0-10">
|
<g id="glyph-0-10">
|
||||||
<path d="M 1.171875 -10.640625 L 3.625 -10.640625 L 3.625 0 L 1.171875 0 Z M 1.171875 -10.640625 "/>
|
<path d="M 4.8125 -6.09375 C 4.269531 -6.09375 3.859375 -5.894531 3.578125 -5.5 C 3.296875 -5.113281 3.15625 -4.554688 3.15625 -3.828125 C 3.15625 -3.085938 3.296875 -2.519531 3.578125 -2.125 C 3.859375 -1.738281 4.269531 -1.546875 4.8125 -1.546875 C 5.351562 -1.546875 5.765625 -1.738281 6.046875 -2.125 C 6.328125 -2.519531 6.46875 -3.085938 6.46875 -3.828125 C 6.46875 -4.554688 6.328125 -5.113281 6.046875 -5.5 C 5.765625 -5.894531 5.351562 -6.09375 4.8125 -6.09375 Z M 4.8125 -7.84375 C 6.132812 -7.84375 7.164062 -7.484375 7.90625 -6.765625 C 8.644531 -6.054688 9.015625 -5.078125 9.015625 -3.828125 C 9.015625 -2.566406 8.644531 -1.578125 7.90625 -0.859375 C 7.164062 -0.148438 6.132812 0.203125 4.8125 0.203125 C 3.5 0.203125 2.46875 -0.148438 1.71875 -0.859375 C 0.976562 -1.578125 0.609375 -2.566406 0.609375 -3.828125 C 0.609375 -5.078125 0.976562 -6.054688 1.71875 -6.765625 C 2.46875 -7.484375 3.5 -7.84375 4.8125 -7.84375 Z M 4.8125 -7.84375 "/>
|
||||||
|
</g>
|
||||||
|
<g id="glyph-0-11">
|
||||||
|
<path d="M 8.875 -4.65625 L 8.875 0 L 6.40625 0 L 6.40625 -3.5625 C 6.40625 -4.226562 6.390625 -4.6875 6.359375 -4.9375 C 6.335938 -5.1875 6.289062 -5.367188 6.21875 -5.484375 C 6.125 -5.648438 5.992188 -5.773438 5.828125 -5.859375 C 5.660156 -5.953125 5.472656 -6 5.265625 -6 C 4.753906 -6 4.351562 -5.800781 4.0625 -5.40625 C 3.769531 -5.007812 3.625 -4.460938 3.625 -3.765625 L 3.625 0 L 1.171875 0 L 1.171875 -7.65625 L 3.625 -7.65625 L 3.625 -6.53125 C 3.988281 -6.976562 4.378906 -7.304688 4.796875 -7.515625 C 5.210938 -7.734375 5.671875 -7.84375 6.171875 -7.84375 C 7.054688 -7.84375 7.726562 -7.570312 8.1875 -7.03125 C 8.644531 -6.488281 8.875 -5.695312 8.875 -4.65625 Z M 8.875 -4.65625 "/>
|
||||||
</g>
|
</g>
|
||||||
<g id="glyph-1-0">
|
<g id="glyph-1-0">
|
||||||
<path d="M 6.4375 -8.78125 L 6.4375 -4.96875 L 10.25 -4.96875 L 10.25 -3.8125 L 6.4375 -3.8125 L 6.4375 0 L 5.296875 0 L 5.296875 -3.8125 L 1.484375 -3.8125 L 1.484375 -4.96875 L 5.296875 -4.96875 L 5.296875 -8.78125 Z M 6.4375 -8.78125 "/>
|
<path d="M 6.4375 -8.78125 L 6.4375 -4.96875 L 10.25 -4.96875 L 10.25 -3.8125 L 6.4375 -3.8125 L 6.4375 0 L 5.296875 0 L 5.296875 -3.8125 L 1.484375 -3.8125 L 1.484375 -4.96875 L 5.296875 -4.96875 L 5.296875 -8.78125 Z M 6.4375 -8.78125 "/>
|
||||||
@@ -121,9 +124,18 @@
|
|||||||
<g id="glyph-1-28">
|
<g id="glyph-1-28">
|
||||||
<path d="M 4.5 0.71875 C 4.144531 1.625 3.796875 2.210938 3.453125 2.484375 C 3.117188 2.765625 2.671875 2.90625 2.109375 2.90625 L 1.109375 2.90625 L 1.109375 1.859375 L 1.84375 1.859375 C 2.1875 1.859375 2.453125 1.773438 2.640625 1.609375 C 2.835938 1.453125 3.050781 1.066406 3.28125 0.453125 L 3.515625 -0.125 L 0.421875 -7.65625 L 1.75 -7.65625 L 4.140625 -1.671875 L 6.53125 -7.65625 L 7.875 -7.65625 Z M 4.5 0.71875 "/>
|
<path d="M 4.5 0.71875 C 4.144531 1.625 3.796875 2.210938 3.453125 2.484375 C 3.117188 2.765625 2.671875 2.90625 2.109375 2.90625 L 1.109375 2.90625 L 1.109375 1.859375 L 1.84375 1.859375 C 2.1875 1.859375 2.453125 1.773438 2.640625 1.609375 C 2.835938 1.453125 3.050781 1.066406 3.28125 0.453125 L 3.515625 -0.125 L 0.421875 -7.65625 L 1.75 -7.65625 L 4.140625 -1.671875 L 6.53125 -7.65625 L 7.875 -7.65625 Z M 4.5 0.71875 "/>
|
||||||
</g>
|
</g>
|
||||||
|
<g id="glyph-1-29">
|
||||||
|
<path d="M 1.1875 -3.015625 L 1.1875 -7.65625 L 2.453125 -7.65625 L 2.453125 -3.0625 C 2.453125 -2.34375 2.59375 -1.800781 2.875 -1.4375 C 3.15625 -1.070312 3.578125 -0.890625 4.140625 -0.890625 C 4.816406 -0.890625 5.351562 -1.101562 5.75 -1.53125 C 6.144531 -1.96875 6.34375 -2.5625 6.34375 -3.3125 L 6.34375 -7.65625 L 7.609375 -7.65625 L 7.609375 0 L 6.34375 0 L 6.34375 -1.171875 C 6.039062 -0.710938 5.6875 -0.367188 5.28125 -0.140625 C 4.875 0.0859375 4.40625 0.203125 3.875 0.203125 C 3 0.203125 2.332031 -0.0664062 1.875 -0.609375 C 1.414062 -1.160156 1.1875 -1.960938 1.1875 -3.015625 Z M 4.359375 -7.84375 Z M 4.359375 -7.84375 "/>
|
||||||
|
</g>
|
||||||
|
<g id="glyph-1-30">
|
||||||
|
<path d="M 6.8125 -3.828125 C 6.8125 -4.742188 6.617188 -5.460938 6.234375 -5.984375 C 5.859375 -6.515625 5.335938 -6.78125 4.671875 -6.78125 C 4.003906 -6.78125 3.476562 -6.515625 3.09375 -5.984375 C 2.71875 -5.460938 2.53125 -4.742188 2.53125 -3.828125 C 2.53125 -2.898438 2.71875 -2.171875 3.09375 -1.640625 C 3.476562 -1.117188 4.003906 -0.859375 4.671875 -0.859375 C 5.335938 -0.859375 5.859375 -1.117188 6.234375 -1.640625 C 6.617188 -2.171875 6.8125 -2.898438 6.8125 -3.828125 Z M 2.53125 -6.5 C 2.800781 -6.945312 3.132812 -7.28125 3.53125 -7.5 C 3.9375 -7.726562 4.421875 -7.84375 4.984375 -7.84375 C 5.910156 -7.84375 6.664062 -7.472656 7.25 -6.734375 C 7.832031 -5.992188 8.125 -5.023438 8.125 -3.828125 C 8.125 -2.617188 7.832031 -1.644531 7.25 -0.90625 C 6.664062 -0.164062 5.910156 0.203125 4.984375 0.203125 C 4.421875 0.203125 3.9375 0.09375 3.53125 -0.125 C 3.132812 -0.351562 2.800781 -0.695312 2.53125 -1.15625 L 2.53125 0 L 1.265625 0 L 1.265625 -10.640625 L 2.53125 -10.640625 Z M 2.53125 -6.5 "/>
|
||||||
|
</g>
|
||||||
|
<g id="glyph-1-31">
|
||||||
|
<path d="M 7.5 -7.65625 L 7.5 0 L 6.234375 0 L 6.234375 -6.671875 L 2.796875 -6.671875 L 2.796875 0 L 1.53125 0 L 1.53125 -6.671875 L 0.328125 -6.671875 L 0.328125 -7.65625 L 1.53125 -7.65625 L 1.53125 -8.1875 C 1.53125 -9.019531 1.722656 -9.632812 2.109375 -10.03125 C 2.503906 -10.4375 3.113281 -10.640625 3.9375 -10.640625 L 5.203125 -10.640625 L 5.203125 -9.59375 L 4 -9.59375 C 3.539062 -9.59375 3.222656 -9.5 3.046875 -9.3125 C 2.878906 -9.132812 2.796875 -8.804688 2.796875 -8.328125 L 2.796875 -7.65625 Z M 6.234375 -10.625 L 7.5 -10.625 L 7.5 -9.03125 L 6.234375 -9.03125 Z M 6.234375 -10.625 "/>
|
||||||
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</defs>
|
</defs>
|
||||||
<path fill="none" stroke-width="2" stroke-linecap="butt" stroke-linejoin="round" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 0.00016626 0 L 311.000166 0 L 311.000166 151 L 0.00016626 151 Z M 0.00016626 0 Z M 0.00016626 0 " transform="matrix(1, 0, 0, 1, 9.898271, 9.042969)"/>
|
<path fill="none" stroke-width="2" stroke-linecap="butt" stroke-linejoin="round" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 0.00016626 0 L 311.000166 0 L 311.000166 185 L 0.00016626 185 Z M 0.00016626 0 Z M 0.00016626 0 " transform="matrix(1, 0, 0, 1, 9.898271, 9.042969)"/>
|
||||||
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
|
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
|
||||||
<use xlink:href="#glyph-0-0" x="122.898438" y="40.042969"/>
|
<use xlink:href="#glyph-0-0" x="122.898438" y="40.042969"/>
|
||||||
<use xlink:href="#glyph-0-1" x="134.898438" y="40.042969"/>
|
<use xlink:href="#glyph-0-1" x="134.898438" y="40.042969"/>
|
||||||
@@ -246,97 +258,110 @@
|
|||||||
<use xlink:href="#glyph-1-9" x="293.898438" y="152.042969"/>
|
<use xlink:href="#glyph-1-9" x="293.898438" y="152.042969"/>
|
||||||
<use xlink:href="#glyph-1-11" x="302.898438" y="152.042969"/>
|
<use xlink:href="#glyph-1-11" x="302.898438" y="152.042969"/>
|
||||||
</g>
|
</g>
|
||||||
<path fill="none" stroke-width="2" stroke-linecap="butt" stroke-linejoin="round" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 0.00103399 0 L 311.001034 0 L 311.001034 117 L 0.00103399 117 Z M 0.00103399 0 Z M 0.00103399 0 " transform="matrix(1, 0, 0, 1, 363.924747, 26.042969)"/>
|
|
||||||
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
|
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
|
||||||
<use xlink:href="#glyph-0-9" x="504.925781" y="57.042969"/>
|
<use xlink:href="#glyph-1-0" x="13.898438" y="169.042969"/>
|
||||||
<use xlink:href="#glyph-0-1" x="514.925781" y="57.042969"/>
|
<use xlink:href="#glyph-1-1" x="25.898438" y="169.042969"/>
|
||||||
<use xlink:href="#glyph-0-10" x="523.925781" y="57.042969"/>
|
<use xlink:href="#glyph-1-10" x="29.898438" y="169.042969"/>
|
||||||
<use xlink:href="#glyph-0-10" x="528.925781" y="57.042969"/>
|
<use xlink:href="#glyph-1-9" x="38.898438" y="169.042969"/>
|
||||||
</g>
|
<use xlink:href="#glyph-1-13" x="47.898438" y="169.042969"/>
|
||||||
<path fill="none" stroke-width="2" stroke-linecap="butt" stroke-linejoin="round" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 0.00103399 41 L 311.001034 41 " transform="matrix(1, 0, 0, 1, 363.924747, 26.042969)"/>
|
<use xlink:href="#glyph-1-5" x="56.898438" y="169.042969"/>
|
||||||
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
|
<use xlink:href="#glyph-1-24" x="61.898438" y="169.042969"/>
|
||||||
<use xlink:href="#glyph-1-0" x="367.925781" y="84.042969"/>
|
<use xlink:href="#glyph-1-14" x="67.898438" y="169.042969"/>
|
||||||
<use xlink:href="#glyph-1-1" x="379.925781" y="84.042969"/>
|
<use xlink:href="#glyph-1-8" x="76.898438" y="169.042969"/>
|
||||||
<use xlink:href="#glyph-1-2" x="383.925781" y="84.042969"/>
|
<use xlink:href="#glyph-1-27" x="85.898438" y="169.042969"/>
|
||||||
<use xlink:href="#glyph-1-3" x="394.925781" y="84.042969"/>
|
<use xlink:href="#glyph-1-23" x="92.898438" y="169.042969"/>
|
||||||
<use xlink:href="#glyph-1-4" x="398.925781" y="84.042969"/>
|
<use xlink:href="#glyph-1-13" x="97.898438" y="169.042969"/>
|
||||||
<use xlink:href="#glyph-1-5" x="407.925781" y="84.042969"/>
|
<use xlink:href="#glyph-1-24" x="106.898438" y="169.042969"/>
|
||||||
<use xlink:href="#glyph-1-6" x="412.925781" y="84.042969"/>
|
<use xlink:href="#glyph-1-11" x="112.898438" y="169.042969"/>
|
||||||
<use xlink:href="#glyph-1-7" x="421.925781" y="84.042969"/>
|
<use xlink:href="#glyph-1-7" x="126.898438" y="169.042969"/>
|
||||||
<use xlink:href="#glyph-1-1" x="426.925781" y="84.042969"/>
|
<use xlink:href="#glyph-1-1" x="131.898438" y="169.042969"/>
|
||||||
<use xlink:href="#glyph-1-3" x="430.925781" y="84.042969"/>
|
<use xlink:href="#glyph-1-4" x="135.898438" y="169.042969"/>
|
||||||
<use xlink:href="#glyph-1-8" x="434.925781" y="84.042969"/>
|
<use xlink:href="#glyph-1-13" x="144.898438" y="169.042969"/>
|
||||||
<use xlink:href="#glyph-1-5" x="443.925781" y="84.042969"/>
|
<use xlink:href="#glyph-1-29" x="153.898438" y="169.042969"/>
|
||||||
|
<use xlink:href="#glyph-1-30" x="162.898438" y="169.042969"/>
|
||||||
|
<use xlink:href="#glyph-1-22" x="171.898438" y="169.042969"/>
|
||||||
|
<use xlink:href="#glyph-1-9" x="175.898438" y="169.042969"/>
|
||||||
|
<use xlink:href="#glyph-1-16" x="184.898438" y="169.042969"/>
|
||||||
</g>
|
</g>
|
||||||
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
|
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
|
||||||
<use xlink:href="#glyph-1-0" x="367.925781" y="101.042969"/>
|
<use xlink:href="#glyph-1-0" x="13.898438" y="186.042969"/>
|
||||||
<use xlink:href="#glyph-1-1" x="379.925781" y="101.042969"/>
|
<use xlink:href="#glyph-1-1" x="25.898438" y="186.042969"/>
|
||||||
<use xlink:href="#glyph-1-6" x="383.925781" y="101.042969"/>
|
<use xlink:href="#glyph-1-31" x="29.898438" y="186.042969"/>
|
||||||
<use xlink:href="#glyph-1-9" x="392.925781" y="101.042969"/>
|
<use xlink:href="#glyph-1-22" x="38.898438" y="186.042969"/>
|
||||||
<use xlink:href="#glyph-1-3" x="401.925781" y="101.042969"/>
|
<use xlink:href="#glyph-1-9" x="42.898438" y="186.042969"/>
|
||||||
<use xlink:href="#glyph-1-10" x="405.925781" y="101.042969"/>
|
<use xlink:href="#glyph-1-21" x="51.898438" y="186.042969"/>
|
||||||
<use xlink:href="#glyph-1-6" x="414.925781" y="101.042969"/>
|
<use xlink:href="#glyph-1-14" x="60.898438" y="186.042969"/>
|
||||||
<use xlink:href="#glyph-1-5" x="423.925781" y="101.042969"/>
|
<use xlink:href="#glyph-1-5" x="69.898438" y="186.042969"/>
|
||||||
<use xlink:href="#glyph-1-7" x="428.925781" y="101.042969"/>
|
<use xlink:href="#glyph-1-6" x="74.898438" y="186.042969"/>
|
||||||
<use xlink:href="#glyph-1-1" x="433.925781" y="101.042969"/>
|
<use xlink:href="#glyph-1-7" x="83.898438" y="186.042969"/>
|
||||||
<use xlink:href="#glyph-1-3" x="437.925781" y="101.042969"/>
|
<use xlink:href="#glyph-1-1" x="88.898438" y="186.042969"/>
|
||||||
<use xlink:href="#glyph-1-8" x="441.925781" y="101.042969"/>
|
<use xlink:href="#glyph-1-25" x="92.898438" y="186.042969"/>
|
||||||
<use xlink:href="#glyph-1-5" x="450.925781" y="101.042969"/>
|
<use xlink:href="#glyph-1-6" x="100.898438" y="186.042969"/>
|
||||||
|
<use xlink:href="#glyph-1-14" x="109.898438" y="186.042969"/>
|
||||||
|
<use xlink:href="#glyph-1-24" x="118.898438" y="186.042969"/>
|
||||||
|
<use xlink:href="#glyph-1-16" x="124.898438" y="186.042969"/>
|
||||||
|
</g>
|
||||||
|
<path fill="none" stroke-width="2" stroke-linecap="butt" stroke-linejoin="round" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 0.0000915527 0 L 130.000092 0 L 130.000092 125 L 0.0000915527 125 Z M 0.0000915527 0 Z M 0.0000915527 0 " transform="matrix(1, 0, 0, 1, 380.788971, 32.691406)"/>
|
||||||
|
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
|
||||||
|
<use xlink:href="#glyph-0-9" x="424.789062" y="63.691406"/>
|
||||||
|
<use xlink:href="#glyph-0-10" x="434.789062" y="63.691406"/>
|
||||||
|
<use xlink:href="#glyph-0-2" x="444.789062" y="63.691406"/>
|
||||||
|
<use xlink:href="#glyph-0-11" x="449.789062" y="63.691406"/>
|
||||||
|
<use xlink:href="#glyph-0-5" x="459.789062" y="63.691406"/>
|
||||||
|
</g>
|
||||||
|
<path fill="none" stroke-width="2" stroke-linecap="butt" stroke-linejoin="round" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 0.0000915527 41 L 130.000092 41 " transform="matrix(1, 0, 0, 1, 380.788971, 32.691406)"/>
|
||||||
|
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
|
||||||
|
<use xlink:href="#glyph-1-0" x="384.789062" y="90.691406"/>
|
||||||
|
<use xlink:href="#glyph-1-1" x="396.789062" y="90.691406"/>
|
||||||
|
<use xlink:href="#glyph-1-15" x="400.789062" y="90.691406"/>
|
||||||
|
<use xlink:href="#glyph-1-7" x="408.789062" y="90.691406"/>
|
||||||
|
<use xlink:href="#glyph-1-1" x="413.789062" y="90.691406"/>
|
||||||
|
<use xlink:href="#glyph-1-3" x="417.789062" y="90.691406"/>
|
||||||
|
<use xlink:href="#glyph-1-8" x="421.789062" y="90.691406"/>
|
||||||
|
<use xlink:href="#glyph-1-5" x="430.789062" y="90.691406"/>
|
||||||
</g>
|
</g>
|
||||||
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
|
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
|
||||||
<use xlink:href="#glyph-1-0" x="367.925781" y="118.042969"/>
|
<use xlink:href="#glyph-1-0" x="384.789062" y="107.691406"/>
|
||||||
<use xlink:href="#glyph-1-1" x="379.925781" y="118.042969"/>
|
<use xlink:href="#glyph-1-1" x="396.789062" y="107.691406"/>
|
||||||
<use xlink:href="#glyph-1-25" x="383.925781" y="118.042969"/>
|
<use xlink:href="#glyph-1-28" x="400.789062" y="107.691406"/>
|
||||||
<use xlink:href="#glyph-1-9" x="391.925781" y="118.042969"/>
|
<use xlink:href="#glyph-1-7" x="407.789062" y="107.691406"/>
|
||||||
<use xlink:href="#glyph-1-22" x="400.925781" y="118.042969"/>
|
<use xlink:href="#glyph-1-1" x="412.789062" y="107.691406"/>
|
||||||
<use xlink:href="#glyph-1-22" x="404.925781" y="118.042969"/>
|
<use xlink:href="#glyph-1-3" x="416.789062" y="107.691406"/>
|
||||||
<use xlink:href="#glyph-1-27" x="408.925781" y="118.042969"/>
|
<use xlink:href="#glyph-1-8" x="420.789062" y="107.691406"/>
|
||||||
<use xlink:href="#glyph-1-7" x="415.925781" y="118.042969"/>
|
<use xlink:href="#glyph-1-5" x="429.789062" y="107.691406"/>
|
||||||
<use xlink:href="#glyph-1-1" x="420.925781" y="118.042969"/>
|
|
||||||
<use xlink:href="#glyph-1-3" x="424.925781" y="118.042969"/>
|
|
||||||
<use xlink:href="#glyph-1-8" x="428.925781" y="118.042969"/>
|
|
||||||
<use xlink:href="#glyph-1-5" x="437.925781" y="118.042969"/>
|
|
||||||
<use xlink:href="#glyph-1-16" x="442.925781" y="118.042969"/>
|
|
||||||
</g>
|
</g>
|
||||||
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
|
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
|
||||||
<use xlink:href="#glyph-1-0" x="367.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-0" x="384.789062" y="124.691406"/>
|
||||||
<use xlink:href="#glyph-1-1" x="379.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-1" x="396.789062" y="124.691406"/>
|
||||||
<use xlink:href="#glyph-1-17" x="383.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-11" x="400.789062" y="124.691406"/>
|
||||||
<use xlink:href="#glyph-1-18" x="394.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-27" x="414.789062" y="124.691406"/>
|
||||||
<use xlink:href="#glyph-1-19" x="405.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-25" x="421.789062" y="124.691406"/>
|
||||||
<use xlink:href="#glyph-1-20" x="415.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-14" x="429.789062" y="124.691406"/>
|
||||||
<use xlink:href="#glyph-1-21" x="424.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-27" x="438.789062" y="124.691406"/>
|
||||||
<use xlink:href="#glyph-1-14" x="433.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-9" x="445.789062" y="124.691406"/>
|
||||||
<use xlink:href="#glyph-1-5" x="442.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-7" x="454.789062" y="124.691406"/>
|
||||||
<use xlink:href="#glyph-1-3" x="447.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-1" x="459.789062" y="124.691406"/>
|
||||||
<use xlink:href="#glyph-1-14" x="451.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-3" x="463.789062" y="124.691406"/>
|
||||||
<use xlink:href="#glyph-1-22" x="460.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-8" x="467.789062" y="124.691406"/>
|
||||||
<use xlink:href="#glyph-1-19" x="464.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-5" x="476.789062" y="124.691406"/>
|
||||||
<use xlink:href="#glyph-1-9" x="473.925781" y="135.042969"/>
|
</g>
|
||||||
<use xlink:href="#glyph-1-23" x="482.925781" y="135.042969"/>
|
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
|
||||||
<use xlink:href="#glyph-1-9" x="487.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-0" x="384.789062" y="141.691406"/>
|
||||||
<use xlink:href="#glyph-1-24" x="496.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-1" x="396.789062" y="141.691406"/>
|
||||||
<use xlink:href="#glyph-1-9" x="501.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-14" x="400.789062" y="141.691406"/>
|
||||||
<use xlink:href="#glyph-1-8" x="510.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-22" x="409.789062" y="141.691406"/>
|
||||||
<use xlink:href="#glyph-1-25" x="519.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-22" x="413.789062" y="141.691406"/>
|
||||||
<use xlink:href="#glyph-1-9" x="527.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-13" x="417.789062" y="141.691406"/>
|
||||||
<use xlink:href="#glyph-1-7" x="536.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-25" x="426.789062" y="141.691406"/>
|
||||||
<use xlink:href="#glyph-1-1" x="541.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-14" x="434.789062" y="141.691406"/>
|
||||||
<use xlink:href="#glyph-1-24" x="545.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-5" x="443.789062" y="141.691406"/>
|
||||||
<use xlink:href="#glyph-1-9" x="550.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-9" x="448.789062" y="141.691406"/>
|
||||||
<use xlink:href="#glyph-1-23" x="559.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-4" x="457.789062" y="141.691406"/>
|
||||||
<use xlink:href="#glyph-1-9" x="564.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-7" x="466.789062" y="141.691406"/>
|
||||||
<use xlink:href="#glyph-1-24" x="573.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-1" x="471.789062" y="141.691406"/>
|
||||||
<use xlink:href="#glyph-1-9" x="578.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-30" x="475.789062" y="141.691406"/>
|
||||||
<use xlink:href="#glyph-1-8" x="587.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-13" x="484.789062" y="141.691406"/>
|
||||||
<use xlink:href="#glyph-1-25" x="596.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-13" x="493.789062" y="141.691406"/>
|
||||||
<use xlink:href="#glyph-1-9" x="604.925781" y="135.042969"/>
|
<use xlink:href="#glyph-1-22" x="502.789062" y="141.691406"/>
|
||||||
<use xlink:href="#glyph-1-26" x="613.925781" y="135.042969"/>
|
|
||||||
<use xlink:href="#glyph-1-27" x="620.925781" y="135.042969"/>
|
|
||||||
<use xlink:href="#glyph-1-28" x="627.925781" y="135.042969"/>
|
|
||||||
<use xlink:href="#glyph-1-27" x="635.925781" y="135.042969"/>
|
|
||||||
<use xlink:href="#glyph-1-5" x="642.925781" y="135.042969"/>
|
|
||||||
<use xlink:href="#glyph-1-9" x="647.925781" y="135.042969"/>
|
|
||||||
<use xlink:href="#glyph-1-11" x="656.925781" y="135.042969"/>
|
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 45 KiB |
BIN
documentation/documentation.pdf
Normal file
BIN
documentation/documentation.pdf
Normal file
Binary file not shown.
BIN
documentation/kurver_ås.png
Normal file
BIN
documentation/kurver_ås.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
3092
documentation/ms_wikipedia.svg
Normal file
3092
documentation/ms_wikipedia.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 88 KiB |
@@ -1,17 +1,53 @@
|
|||||||
## Group: Trygve and Esther
|
% INF205 Project report
|
||||||
# Functionality
|
% Creating contour maps from raster heightmaps
|
||||||
Our program usees the marching squares algorithm to create a vector contour map from a raster heightmap.
|
% Esther and Trygve 7. May 2024
|
||||||
The cli interface is `gdal_contour [OPTIONS] <src_filename> <dst_filename>` with these options:
|
|
||||||
```
|
|
||||||
-i <elevation intervall> Interval between contours
|
|
||||||
-f <format> Fileformat to output
|
|
||||||
```
|
|
||||||
# data structure and input/output
|
|
||||||

|
|
||||||
# Responsibilities:
|
|
||||||
Esther will create the algorithm itself with multitreading. This will essentially be a function that takes a grid of pixels as input and returns a similar grid of cells.
|
|
||||||
Trygve will take care of reading in the tiff file into our own datastructure and creating a vector image from the output of the algorithm.
|
|
||||||
|
|
||||||
# How do you plan to make it easily verifiable that your objectives are reached?
|

|
||||||
We can compare against the `gdal_contour` cli program which is a implementation widely used in other software. We can compare speed, memory usage and the result itself.
|
# Introduction
|
||||||
Each step in our program also produces a output which we can be worked on and evaluated independently.
|
The source code is available on [https://gitlab.com/Trygve/contour-creator](https://gitlab.com/Trygve/contour-creator)
|
||||||
|
The branch 7-5-2024 contains the source code as it was on the day of the deadline.
|
||||||
|
Becouse of the 90/90 rule we had to focus on the core functionality and unfortunatly we have a few bugs and performance issues left. If we fix these issues they will be available on the main branch.
|
||||||
|
# Functionality
|
||||||
|
Our program uses the marching squares algorithm to create a vector contour map from a raster heightmap (DTM).
|
||||||
|
It outputs a geojson file that can be read by gis software. [https://mapshaper.org/](https://mapshaper.org/) is a simple website where you can view geojson files.
|
||||||
|
|
||||||
|
Instructions on running the program is available in the README.md file
|
||||||
|
# data structures and input/output
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The [HeightMap](https://trygve.gitlab.io/contour-creator/classHeightMap.html) class stores the heightmap as a array on the heap. It was not nececarry to copy or move this class, so we did not implement copy or move constructors/assignment operators.
|
||||||
|
|
||||||
|
The [Point](https://trygve.gitlab.io/contour-creator/classPoint.html) class represents a point in a line. The points are stored in vectors, wich are again stored in a big vector containing all the lines.
|
||||||
|
The `produce_cellmap` function finds the points for a contour at a given elevation. The `create_lines` uses `produce_cellmap` the get the points and then sorts them into contiguous lines before returning a `std::vector<std::vector<Point>>`{.cpp} thats used by the function `write_output_file` to write them to a geojson file.
|
||||||
|
|
||||||
|
# Performance
|
||||||
|
|
||||||
|
| | 1 | 2 | 3 | 4 | 5 | Avarage |
|
||||||
|
| ---------- | ----- | ----- | ----- | ----- | ----- | ------- |
|
||||||
|
| 12 threads | 7,03 | 6,74 | 6,88 | 6,86 | 6,85 | 6,872 |
|
||||||
|
| 1 thread | 21,48 | 21,27 | 20,82 | 20,35 | 20,81 | 20,946 |
|
||||||
|
| reference | 4,5 | 5,61 | 5,24 | 4,81 | 5,02 | 5,036 |
|
||||||
|
|
||||||
|
| | slowdown |
|
||||||
|
| ---------------------------- | -------- |
|
||||||
|
| ours/gdal | 1,36 |
|
||||||
|
| singlethreaded/multithreaded | 3,05 |
|
||||||
|
|
||||||
|
|
||||||
|
Our program is 1,36 times slower than gdal_contour wich is bad considering ours is multithreaded and gdals is not. On the other hand gdals is has 11 years of development.
|
||||||
|
|
||||||
|
The running on 12 threads yields a nice speedup 3,05 times.
|
||||||
|
|
||||||
|
The program assumes the whole heightmap can fit in memory, this is not a problem for heightmaps from [hoydedata.no](https://hoydedata.no/LaserInnsyn2/) as the maximum tile size is about 700mb.
|
||||||
|
|
||||||
|
# Functionality of special interest
|
||||||
|
|
||||||
|
## Trygve
|
||||||
|
I can created a blur method that performs a simple box blur on the whole heigtmap. This is usefull to smooth out noise and create a more readable output.
|
||||||
|
I wanted to implement a gaussion blur, and the code is ready for that, but in the end we became short on time.
|
||||||
|
The blur is activated by passing the `--blur` flag
|
||||||
|
|
||||||
|
## Esther
|
||||||
|
Calculated some statistical estimates for the average, variance and standard deviation for the pixels in the image. Also tried to calculate the steepness around each pixel. However, we didn't manage to get the Gdal to work for this.
|
||||||
|
Statistics output is activated by passing the `--stats` flag
|
||||||
@@ -5,16 +5,31 @@ author:
|
|||||||
- Trygve og Esther
|
- Trygve og Esther
|
||||||
|
|
||||||
---
|
---
|
||||||
# What are contours
|
|
||||||
|
|
||||||
# Output of our program
|
# Output of our program:
|
||||||
pretty pictures
|

|
||||||
|
|
||||||
|
# Marching squares
|
||||||
|

|
||||||
|
|
||||||
# The logical flow of our program
|
# The logical flow of our program
|
||||||
flowchart
|
```cpp
|
||||||
|
int main(int argc, const char* argv[])
|
||||||
# GDAL
|
{
|
||||||
What is it?
|
const char* filepath = argv[1];
|
||||||
|
HeightMap map(filepath);
|
||||||
|
map.blur(0.8)
|
||||||
|
auto lines = create_lines(&map, 5);
|
||||||
|
write_output_file(cellmaps, "out.geojson", &map);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# Performance
|
# Performance
|
||||||
show the benchmarks
|
| | Time |
|
||||||
|
|--------------------|--------------------|
|
||||||
|
| 12 threads | 41s |
|
||||||
|
| 1 thread | 116s |
|
||||||
|
|
||||||
|
|
||||||
|
# Problems we encountered
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
<PAMDataset>
|
|
||||||
<PAMRasterBand band="1">
|
|
||||||
<Metadata>
|
|
||||||
<MDI key="STATISTICS_MAXIMUM">50.05460357666</MDI>
|
|
||||||
<MDI key="STATISTICS_MEAN">43.851418194979</MDI>
|
|
||||||
<MDI key="STATISTICS_MINIMUM">37.187175750732</MDI>
|
|
||||||
<MDI key="STATISTICS_STDDEV">3.3140235738276</MDI>
|
|
||||||
<MDI key="STATISTICS_VALID_PERCENT">100</MDI>
|
|
||||||
</Metadata>
|
|
||||||
</PAMRasterBand>
|
|
||||||
</PAMDataset>
|
|
||||||
3
example_files/odderøya.tif
Normal file
3
example_files/odderøya.tif
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:28f96c6f7191c6577bcdb872b5c6f7c0cd73d5431a304f556bf6c66ec7d7650d
|
||||||
|
size 149951469
|
||||||
1
extern/argh
vendored
Submodule
1
extern/argh
vendored
Submodule
Submodule extern/argh added at 431bf323ac
@@ -1,20 +0,0 @@
|
|||||||
#include <cstdint>
|
|
||||||
#include <gdal/ogr_spatialref.h>
|
|
||||||
#include <ogr_spatialref.h>
|
|
||||||
#include "contour_creator.hh"
|
|
||||||
|
|
||||||
|
|
||||||
CellMap::CellMap(int width, int height, uint8_t* cells, OGRSpatialReference reference_system, double* geotransform)
|
|
||||||
{
|
|
||||||
this->width = width;
|
|
||||||
this->height = height;
|
|
||||||
this->cells = cells;
|
|
||||||
this->reference_system = reference_system;
|
|
||||||
this->geotransform = geotransform;
|
|
||||||
}
|
|
||||||
int CellMap::get_cell(int x, int y)
|
|
||||||
{
|
|
||||||
// all the cells are in an array of ints from left to right, top to bottom
|
|
||||||
int offset = ((this->width * y) + x);
|
|
||||||
return *(this->cells + offset);
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#include <cstddef>
|
||||||
#include <gdal/cpl_conv.h>
|
#include <gdal/cpl_conv.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
@@ -5,7 +6,6 @@
|
|||||||
#include <gdal/gdal.h>
|
#include <gdal/gdal.h>
|
||||||
#include "gdal/gdal_priv.h"
|
#include "gdal/gdal_priv.h"
|
||||||
#include <gdal/gdal_frmts.h>
|
#include <gdal/gdal_frmts.h>
|
||||||
|
|
||||||
#include "contour_creator.hh"
|
#include "contour_creator.hh"
|
||||||
|
|
||||||
|
|
||||||
@@ -30,24 +30,24 @@ HeightMap::HeightMap(const char* filepath)
|
|||||||
this->min = band->GetMinimum();
|
this->min = band->GetMinimum();
|
||||||
this->max = band-> GetMaximum();
|
this->max = band-> GetMaximum();
|
||||||
//https://gdal.org/api/gdaldataset_cpp.html#_CPPv4N11GDALDataset15GetGeoTransformEPd
|
//https://gdal.org/api/gdaldataset_cpp.html#_CPPv4N11GDALDataset15GetGeoTransformEPd
|
||||||
this->geotransform = (double *) CPLMalloc(sizeof(double)*6);
|
this->geotransform = new double[6];
|
||||||
this->reference_system = *(file->GetSpatialRef());
|
this->reference_system = *(file->GetSpatialRef());
|
||||||
|
this->filepath = filepath;
|
||||||
|
|
||||||
file->GetGeoTransform(this->geotransform);
|
file->GetGeoTransform(this->geotransform);
|
||||||
|
|
||||||
this->data = (float *) CPLMalloc(sizeof(float)*width*height);
|
this->data = new float[width*height];
|
||||||
CPLErr error = band->RasterIO( GF_Read, 0, 0, width, height,
|
CPLErr error = band->RasterIO( GF_Read, 0, 0, width, height,
|
||||||
this->data, width, height, GDT_Float32,
|
this->data, width, height, GDT_Float32,
|
||||||
0, 0 );
|
0, 0 );
|
||||||
if (error) { throw std::runtime_error("Could not read tif file!"); }
|
if (error) { throw std::runtime_error("Could not read tif file!"); }
|
||||||
band->FlushCache();
|
band->FlushCache();
|
||||||
|
const int size = this->width*this->height;
|
||||||
}
|
}
|
||||||
float HeightMap::get_pixel(int x, int y)
|
float HeightMap::get_pixel(int x, int y)
|
||||||
{
|
{
|
||||||
// all the pixels are in an array of floats from left to right, top to bottom
|
// all the pixels are in an array of floats from left to right, top to bottom
|
||||||
int offset = ((this->width * y) + x);
|
return this->data[((this->width * y) + x)];
|
||||||
//std::cout << " offset: " << offset << " " << x << ","<< y;
|
|
||||||
return *(this->data + offset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HeightMap::blur(float standard_deviation)
|
void HeightMap::blur(float standard_deviation)
|
||||||
@@ -56,13 +56,13 @@ void HeightMap::blur(float standard_deviation)
|
|||||||
int kernel_height = 5;
|
int kernel_height = 5;
|
||||||
int kernel_width = 5;
|
int kernel_width = 5;
|
||||||
int kernel_size = kernel_height*kernel_width;
|
int kernel_size = kernel_height*kernel_width;
|
||||||
float* kernel = (float*) CPLMalloc(sizeof(float)*kernel_size);
|
float* kernel = new float [kernel_size];
|
||||||
for (int i=0; i<kernel_size; i++)
|
for (int i=0; i<kernel_size; i++)
|
||||||
{
|
{
|
||||||
*(kernel + i) = 1.0;
|
kernel[i] = 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
float* blurred = (float *) CPLMalloc(sizeof(float)*this->width*this->height);
|
float* blurred = new float[this->width*this->height];
|
||||||
#pragma omp parallel
|
#pragma omp parallel
|
||||||
{
|
{
|
||||||
#pragma omp for
|
#pragma omp for
|
||||||
@@ -84,10 +84,108 @@ void HeightMap::blur(float standard_deviation)
|
|||||||
}
|
}
|
||||||
blurred_pixel += this->get_pixel(x, y);
|
blurred_pixel += this->get_pixel(x, y);
|
||||||
}
|
}
|
||||||
*(blurred + i) = blurred_pixel/float(kernel_size);
|
blurred[i] = blurred_pixel/float(kernel_size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
free(this->data);
|
|
||||||
|
delete[] kernel;
|
||||||
|
delete[] this->data;
|
||||||
this->data = blurred;
|
this->data = blurred;
|
||||||
blurred = nullptr;
|
blurred = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeightMap::statistics()
|
||||||
|
{
|
||||||
|
|
||||||
|
float avg = 0;
|
||||||
|
float var = 0;
|
||||||
|
float std = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < this->width*this->height; i++)
|
||||||
|
{int x = i%this->width;
|
||||||
|
int y = i/this->width;
|
||||||
|
if (x<0 || x>=this->width)
|
||||||
|
{
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y<0 || y>=this->height)
|
||||||
|
{
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
avg += this->get_pixel(x, y);}
|
||||||
|
//std::cout << this->get_pixel(x, y);}
|
||||||
|
|
||||||
|
avg = avg/(this->width*this->height);
|
||||||
|
std::cout << "Average value: " << avg <<"\n";
|
||||||
|
|
||||||
|
for (int i = 0; i < this->width*this->height; i++)
|
||||||
|
{
|
||||||
|
int x = i%this->width;
|
||||||
|
int y = i/this->width;
|
||||||
|
var += (avg - this->get_pixel(x, y))*(avg - this->get_pixel(x, y));
|
||||||
|
}
|
||||||
|
std = sqrt(var)/(this->width*this->height-1);
|
||||||
|
var = var / (this->width*this->height-1);
|
||||||
|
std::cout << "std: " << std << "\n";
|
||||||
|
std::cout << "var: " << var << "\n";
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void HeightMap::calculate_steepness(const char* filepath)
|
||||||
|
{
|
||||||
|
int kernel_height = 5;
|
||||||
|
int kernel_width = 5;
|
||||||
|
int kernel_size = kernel_height*kernel_width;
|
||||||
|
|
||||||
|
float* kernel = new float[kernel_size];
|
||||||
|
float* steepness = new float[this->width*this->height];
|
||||||
|
#pragma omp parallel
|
||||||
|
{
|
||||||
|
#pragma omp for
|
||||||
|
for (int i = 0; i < this->height * this->width; i++)
|
||||||
|
{
|
||||||
|
int x = i%this->width;
|
||||||
|
int y = i/this->width;
|
||||||
|
float max = 0;
|
||||||
|
float min = 0;
|
||||||
|
for (int j = 0; j < kernel_size; j++)
|
||||||
|
{
|
||||||
|
if (this->get_pixel(x, y) > max)
|
||||||
|
{
|
||||||
|
max = get_pixel(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->get_pixel(x, y) < min)
|
||||||
|
{
|
||||||
|
min = get_pixel(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x<0 || x>=this->width)
|
||||||
|
{
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y<0 || y>=this->height)
|
||||||
|
{
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
steepness[i] = max - min;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GDALAllRegister();
|
||||||
|
GDALDataset *original_file = (GDALDataset *) GDALOpen( this->filepath, GA_ReadOnly );
|
||||||
|
|
||||||
|
auto driver = GetGDALDriverManager()->GetDriverByName("GeoRaster");
|
||||||
|
GDALDataset *file;
|
||||||
|
file = driver->CreateCopy(filepath, original_file, FALSE, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
file->AddBand(GDT_Float32, NULL);
|
||||||
|
GDALRasterBand *band = file->GetRasterBand(0);
|
||||||
|
CPLErr error = band->RasterIO( GF_Write, 0, 0, this->width, this->height,
|
||||||
|
steepness, this->width, this->height, GDT_Float32,
|
||||||
|
0, 0 );
|
||||||
|
GDALClose(file);
|
||||||
}
|
}
|
||||||
@@ -15,26 +15,18 @@ class HeightMap
|
|||||||
int height; //!< height of image in pixels
|
int height; //!< height of image in pixels
|
||||||
float min; //!< Minimum value in image
|
float min; //!< Minimum value in image
|
||||||
float max; //!< Maximum value in image
|
float max; //!< Maximum value in image
|
||||||
|
const char* filepath; //!< Where the heightmap was read from
|
||||||
OGRSpatialReference reference_system;
|
OGRSpatialReference reference_system;
|
||||||
|
|
||||||
HeightMap(const char* filepath);
|
HeightMap(const char* filepath);
|
||||||
|
~HeightMap()
|
||||||
|
{
|
||||||
|
delete[] data;
|
||||||
|
delete[] geotransform;
|
||||||
|
}
|
||||||
float get_pixel(int x,int y);
|
float get_pixel(int x,int y);
|
||||||
void blur(float standard_deviation);
|
void blur(float standard_deviation);
|
||||||
};
|
void statistics(); //!< Print statistical information about the heightmap
|
||||||
|
void calculate_steepness(const char*); //!< Output a raster file with the steepness of each pixel
|
||||||
/**
|
|
||||||
@brief stores the cells from marching squars for one elevation level
|
|
||||||
*/
|
|
||||||
class CellMap
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
uint8_t* cells; //!< pointer to the first cell in the array. uint8_t is a 8 bit unsigned integer
|
|
||||||
double* geotransform; //!< Six double buffer for storing the affine transformations
|
|
||||||
int width; //!< width of image in cells
|
|
||||||
int height; //!< height of image in cells
|
|
||||||
OGRSpatialReference reference_system;
|
|
||||||
CellMap(int width, int height, uint8_t* cells, OGRSpatialReference reference_system, double* geotransform);
|
|
||||||
int get_cell(int x,int y);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Point
|
class Point
|
||||||
@@ -42,11 +34,11 @@ class Point
|
|||||||
public:
|
public:
|
||||||
int x;
|
int x;
|
||||||
int y;
|
int y;
|
||||||
int mscase;
|
uint8_t mscase; //!< The case outputted by the algorithm. A number between 0 and 15
|
||||||
Point(int x, int y, int mscase){
|
bool allocated = false; //!< Used when sorting the points to see if it has been sorted
|
||||||
|
Point(int x, int y, uint8_t mscase){
|
||||||
this -> x = x;
|
this -> x = x;
|
||||||
this -> y = y;
|
this -> y = y;
|
||||||
this -> mscase = mscase;
|
this -> mscase = mscase;
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
295
src/main.cpp
295
src/main.cpp
@@ -1,4 +1,6 @@
|
|||||||
#include "contour_creator.hh"
|
#include "contour_creator.hh"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <gdal/ogr_api.h>
|
#include <gdal/ogr_api.h>
|
||||||
#include "ogrsf_frmts.h"
|
#include "ogrsf_frmts.h"
|
||||||
#include <gdal/ogr_core.h>
|
#include <gdal/ogr_core.h>
|
||||||
@@ -6,41 +8,48 @@
|
|||||||
#include <gdal/ogr_geometry.h>
|
#include <gdal/ogr_geometry.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <tuple>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <gdal/gdal.h>
|
#include <gdal/gdal.h>
|
||||||
#include "gdal/gdal_priv.h"
|
#include "gdal/gdal_priv.h"
|
||||||
#include <gdal/gdal_frmts.h>
|
#include <gdal/gdal_frmts.h>
|
||||||
#include <omp.h>
|
#include <omp.h>
|
||||||
|
#include "argh.h"
|
||||||
|
|
||||||
std::vector<Point> produce_cellmap(HeightMap* heightmap, float z)
|
std::vector<Point> produce_cellmap(HeightMap* heightmap, float z)
|
||||||
{
|
{
|
||||||
int length = (heightmap->width-1)*(heightmap->height-1);
|
int length = (heightmap->width-1)*(heightmap->height-1); // Defining length of vector
|
||||||
uint8_t *cells = (uint8_t *) CPLMalloc(sizeof(uint8_t)*length);
|
std::vector<Point> points; // Initiating a vector of points
|
||||||
std::vector<Point> points;
|
|
||||||
for (int i = 0; i<length; i++) {
|
for (int i = 0; i<length; i++) {
|
||||||
int y = i/(heightmap->width-1);
|
int y = i/(heightmap->width-1);
|
||||||
int x = i%(heightmap->width-1);
|
int x = i%(heightmap->width-1);
|
||||||
uint8_t result = (heightmap->get_pixel(x,y)>z) +
|
uint8_t result = (heightmap->get_pixel(x,y)>z)*8 +
|
||||||
(heightmap->get_pixel(x+1,y)>z)*2 +
|
(heightmap->get_pixel(x+1,y)>z)*4 +
|
||||||
(heightmap->get_pixel(x+1,y+1)>z)*4 +
|
(heightmap->get_pixel(x+1,y+1)>z)*2 +
|
||||||
(heightmap->get_pixel(x,y+1)>z)*8;
|
(heightmap->get_pixel(x,y+1)>z);
|
||||||
if (result != 0 and result != 15 ) {
|
if (result != 0 and result != 15 ) {
|
||||||
points.push_back(Point(x, y, result));
|
points.push_back(Point(x, y, result));
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return points;
|
return points;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::vector<Point>> vector_cellmap(HeightMap* heightmap, int interval)
|
bool is_in(int value, std::array<int, 8> array)
|
||||||
|
{
|
||||||
|
return std::binary_search(array.begin(), array.end(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::vector<Point>> create_lines(HeightMap* heightmap, int interval)
|
||||||
{
|
{
|
||||||
int num_contours = (heightmap->max - heightmap->min)/interval;
|
int num_contours = (heightmap->max - heightmap->min)/interval;
|
||||||
std::vector<std::vector<Point>> vector_contours;
|
std::vector<std::vector<Point>> vector_contours;
|
||||||
omp_set_num_threads(12);
|
|
||||||
|
std::array<int,8> north{4, 5,6,7,8,9,10,11};
|
||||||
|
std::array<int,8> south{1,2,5,6,9,10,13,14};
|
||||||
|
std::array<int,8> east{2,3,4,5,10,11,12,13};
|
||||||
|
std::array<int,8> west{1,3,5,7,8,10,12,14};
|
||||||
|
|
||||||
#pragma omp parallel
|
#pragma omp parallel
|
||||||
{
|
{
|
||||||
@@ -48,11 +57,82 @@ std::vector<std::vector<Point>> vector_cellmap(HeightMap* heightmap, int interva
|
|||||||
#pragma omp for
|
#pragma omp for
|
||||||
for (int i = 1; i <= num_contours; i++)
|
for (int i = 1; i <= num_contours; i++)
|
||||||
{
|
{
|
||||||
vec_private.push_back(produce_cellmap(heightmap, heightmap->min + interval*i));
|
auto points = produce_cellmap(heightmap, heightmap->min + interval*i);
|
||||||
|
std::vector<Point> line;
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
int points_allocated = 0;
|
||||||
|
x = points[0].x;
|
||||||
|
y = points[0].y;
|
||||||
|
int current_case = points[0].mscase;
|
||||||
|
points[0].allocated = true;
|
||||||
|
line.push_back(points[0]);
|
||||||
|
for (int j = 0; j < points.size(); j++){
|
||||||
|
for (int k = 0; k < points.size(); k++){
|
||||||
|
Point* candidate = &points[k];
|
||||||
|
if (!candidate->allocated) {
|
||||||
|
if (candidate->x == x +1 && candidate->y == y /*&& is_in(current_case, east)*/) {
|
||||||
|
candidate->allocated = true;
|
||||||
|
x = candidate->x;
|
||||||
|
y = candidate->y;
|
||||||
|
current_case = candidate->mscase;
|
||||||
|
line.push_back(*candidate);
|
||||||
|
points_allocated++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (candidate->x == x -1 && candidate->y == y /*&& is_in(current_case, west)*/) {
|
||||||
|
x = candidate->x;
|
||||||
|
y = candidate->y;
|
||||||
|
current_case = candidate->mscase;
|
||||||
|
candidate->allocated = true;
|
||||||
|
line.push_back(*candidate);
|
||||||
|
points_allocated++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (candidate->x == x && candidate->y == y + 1 /*&& is_in(current_case, north)*/) {
|
||||||
|
x = candidate->x;
|
||||||
|
y = candidate->y;
|
||||||
|
current_case = candidate->mscase;
|
||||||
|
candidate->allocated = true;
|
||||||
|
line.push_back(*candidate);
|
||||||
|
points_allocated++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (candidate->x == x && candidate->y == y - 1 /*&& is_in(current_case, south)*/) {
|
||||||
|
x = candidate->x;
|
||||||
|
y = candidate->y;
|
||||||
|
current_case = candidate->mscase;
|
||||||
|
candidate->allocated = true;
|
||||||
|
line.push_back(*candidate);
|
||||||
|
points_allocated++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (j > points_allocated)
|
||||||
|
{
|
||||||
|
vec_private.push_back(line);
|
||||||
|
line.clear();
|
||||||
|
//std::cout << points.size() << " ";
|
||||||
|
for (int k = 0; k < points.size(); k++){
|
||||||
|
Point* candidate = &points[k];
|
||||||
|
if (!candidate->allocated)
|
||||||
|
{
|
||||||
|
line.push_back(*candidate);
|
||||||
|
candidate->allocated = true;
|
||||||
|
x = candidate->x;
|
||||||
|
y = candidate->y;
|
||||||
|
current_case = candidate->mscase;
|
||||||
|
points_allocated++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#pragma omp critical
|
#pragma omp critical
|
||||||
vector_contours.insert(vector_contours.end(), vec_private.begin(), vec_private.end());
|
|
||||||
|
|
||||||
|
vector_contours.insert(vector_contours.end(), vec_private.begin(), vec_private.end());
|
||||||
}
|
}
|
||||||
return vector_contours;
|
return vector_contours;
|
||||||
}
|
}
|
||||||
@@ -70,23 +150,38 @@ std::tuple<double, double> local_to_projected(double* geotransform, int x, int y
|
|||||||
constexpr std::tuple<double, double, double, double> marching_squares_lookup(int mcase)
|
constexpr std::tuple<double, double, double, double> marching_squares_lookup(int mcase)
|
||||||
{
|
{
|
||||||
switch (mcase) {
|
switch (mcase) {
|
||||||
case 1: return {0, 0.5, 0.5, 0};
|
case 1:
|
||||||
case 2: return {1, 0.5, 0.5, 0};
|
return {0, 0.5, 0.5, 0};
|
||||||
case 3: return {0, 0.5, 1, 0};
|
case 14:
|
||||||
case 4: return {0.5, 1, 1, 0.5};
|
return {0.5, 0, 0, 0.5};
|
||||||
case 5: return {0, 0, 0, 0}; //FIXME
|
case 2:
|
||||||
case 6: return {0.5, 1, 0.5, 0};
|
return {0.5, 0, 1, 0.5};
|
||||||
case 7: return {0, 0.5, 0.5, 1};
|
case 13:
|
||||||
case 8: return {0, 0.5, 0.5, 1};
|
return {1, 0.5, 0.5, 0};
|
||||||
case 9: return {0.5, 1, 0.5, 0};
|
case 3:
|
||||||
case 10: return {0, 0, 0, 0}; //FIXME
|
return {0, 0.5, 1, 0.5};
|
||||||
case 11: return {0.5, 1, 1, 0.5};
|
case 12:
|
||||||
case 12: return {0, 0.5, 1, 0.5};
|
return {1, 0.5, 0, 0.5};
|
||||||
case 13: return {0.5, 0, 1, 0.5};
|
case 4:
|
||||||
case 14: return {0, 0.5, 0.5, 0};
|
return {0.5, 1, 1, 0.5};
|
||||||
|
case 11:
|
||||||
|
return {1, 0.5, 0.5, 1};
|
||||||
|
case 5:
|
||||||
|
case 10:
|
||||||
|
return {0, 0, 0, 0}; //FIXME
|
||||||
|
case 6:
|
||||||
|
return {0.5, 1, 0.5, 0};
|
||||||
|
case 9:
|
||||||
|
return {0.5, 0, 0.5, 1};
|
||||||
|
case 7:
|
||||||
|
return {0, 0.5, 0.5, 1};
|
||||||
|
case 8:
|
||||||
|
return {0.5, 1, 0, 0.5};
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
void write_output_file(std::vector<CellMap> cellmaps, const char *filepath)
|
void write_output_file(std::vector<std::vector<Point>> all_points, const char *filepath, HeightMap* heightmap)
|
||||||
{
|
{
|
||||||
|
|
||||||
const char *pszDriverName = "GeoJSON";
|
const char *pszDriverName = "GeoJSON";
|
||||||
@@ -112,7 +207,7 @@ void write_output_file(std::vector<CellMap> cellmaps, const char *filepath)
|
|||||||
|
|
||||||
OGRLayer *poLayer;
|
OGRLayer *poLayer;
|
||||||
|
|
||||||
poLayer = poDS->CreateLayer( "contours", &(cellmaps[0]).reference_system, wkbLineString, NULL );
|
poLayer = poDS->CreateLayer( "contours", &heightmap->reference_system, wkbLineString, NULL );
|
||||||
if( poLayer == NULL )
|
if( poLayer == NULL )
|
||||||
{
|
{
|
||||||
printf( "Layer creation failed.\n" );
|
printf( "Layer creation failed.\n" );
|
||||||
@@ -128,61 +223,111 @@ void write_output_file(std::vector<CellMap> cellmaps, const char *filepath)
|
|||||||
printf( "Creating Name field failed.\n" );
|
printf( "Creating Name field failed.\n" );
|
||||||
exit( 1 );
|
exit( 1 );
|
||||||
}
|
}
|
||||||
for (int j = 0; j < cellmaps.size(); j++)
|
int width = heightmap->width -1;
|
||||||
|
int height = heightmap->height -1;
|
||||||
|
int size = width * height;
|
||||||
|
|
||||||
|
for (int j = 0; j < all_points.size(); j++)
|
||||||
{
|
{
|
||||||
CellMap* cellmap = &cellmaps[j];
|
OGRFeature *feature;
|
||||||
|
OGRLineString *geometry = new OGRLineString();
|
||||||
|
feature = OGRFeature::CreateFeature( poLayer->GetLayerDefn() );
|
||||||
|
feature->SetField( "Name", j );
|
||||||
|
|
||||||
for (int i = 0; i < cellmap->height*cellmap->width; i++)
|
std::vector<Point> points = all_points[j];
|
||||||
{
|
|
||||||
if (*(cellmap->cells + i) != 0 && *(cellmap->cells + i) != 15)
|
|
||||||
{
|
|
||||||
OGRFeature *feature;
|
|
||||||
OGRLineString *geometry = new OGRLineString();
|
|
||||||
feature = OGRFeature::CreateFeature( poLayer->GetLayerDefn() );
|
|
||||||
feature->SetField( "Name", j );
|
|
||||||
int x_raw = i%cellmap->width;
|
|
||||||
int y_raw = i/cellmap->width;
|
|
||||||
auto [x, y] = local_to_projected(cellmap->geotransform, x_raw, y_raw);
|
|
||||||
auto [x_bl, y_bl, x_tr, y_tr] = marching_squares_lookup(*(cellmap->cells + i) );
|
|
||||||
//std::cout << x << ", " << y << " ";
|
|
||||||
geometry->setPoint( geometry->getNumPoints(), x+x_tr*0.25, y+y_tr*0.25 );
|
|
||||||
geometry->setPoint( geometry->getNumPoints(), x+x_bl*0.25, y+y_bl*0.25 );
|
|
||||||
|
|
||||||
|
|
||||||
if ( feature->SetGeometry(geometry) != OGRERR_NONE)
|
|
||||||
{
|
|
||||||
printf( "Failed to set geometry.\n" );
|
|
||||||
exit( 1 );
|
|
||||||
}
|
|
||||||
OGRGeometryFactory::destroyGeometry(geometry);
|
|
||||||
if( poLayer->CreateFeature( feature ) != OGRERR_NONE )
|
|
||||||
{
|
|
||||||
printf( "Failed to create feature in shapefile.\n" );
|
|
||||||
exit( 1 );
|
|
||||||
}
|
|
||||||
OGRFeature::DestroyFeature( feature );
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
bool left_to_right = true;
|
||||||
|
|
||||||
|
if ((points[0].y - points[1].y) < 0)
|
||||||
|
left_to_right = true;
|
||||||
|
else
|
||||||
|
left_to_right = false;
|
||||||
|
|
||||||
|
for (int k = 0; k < points.size(); k++)
|
||||||
|
{
|
||||||
|
bool left_to_right = true;
|
||||||
|
|
||||||
|
if ((points[k].x - points[k-1].x) < 0)
|
||||||
|
left_to_right = false;
|
||||||
|
bool top_to_bottom = true;
|
||||||
|
if ((points[k].y - points[k-1].y) < 0)
|
||||||
|
top_to_bottom = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int x_raw = points[k].x;
|
||||||
|
int y_raw = points[k].y;
|
||||||
|
auto [x, y] = local_to_projected(heightmap->geotransform, x_raw, y_raw);
|
||||||
|
|
||||||
|
auto [x1, y1, x2, y2] = marching_squares_lookup(points[k].mscase);
|
||||||
|
|
||||||
|
if (left_to_right or top_to_bottom)
|
||||||
|
{
|
||||||
|
geometry->setPoint(geometry->getNumPoints(),x + (x1/4) ,y + (y1/4));
|
||||||
|
geometry->setPoint(geometry->getNumPoints(),x + (x2/4) ,y + (y2/4));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
geometry->setPoint(geometry->getNumPoints(),x + (x2/4) ,y + (y2/4));
|
||||||
|
geometry->setPoint(geometry->getNumPoints(),x + (x1/4) ,y + (y1/4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( feature->SetGeometry(geometry) != OGRERR_NONE)
|
||||||
|
{
|
||||||
|
printf( "Failed to set geometry.\n" );
|
||||||
|
exit( 1 );
|
||||||
|
}
|
||||||
|
OGRGeometryFactory::destroyGeometry(geometry);
|
||||||
|
if( poLayer->CreateFeature( feature ) != OGRERR_NONE )
|
||||||
|
{
|
||||||
|
printf( "Failed to create feature in shapefile.\n" );
|
||||||
|
exit( 1 );
|
||||||
|
}
|
||||||
|
OGRFeature::DestroyFeature( feature );
|
||||||
}
|
}
|
||||||
|
|
||||||
GDALClose( poDS );
|
GDALClose( poDS );
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, const char* argv[])
|
int main(int argc, const char* argv[])
|
||||||
{
|
{
|
||||||
const char* filepath = argv[1];
|
argh::parser cmdl(argv);
|
||||||
HeightMap map(filepath);
|
|
||||||
|
|
||||||
std::cout << "x: " << map.width << " y: " << map.height << "\n";
|
|
||||||
std::cout << "max: " << map.max << " min: " << map.min << "\n";
|
|
||||||
|
|
||||||
map.blur(0.8);
|
if (cmdl[{ "-h", "--help" }])
|
||||||
|
{
|
||||||
|
std::cout << "Usage:\n"
|
||||||
|
<< "contour_creator [OPTIONS] <input_file>\n\n"
|
||||||
|
<< "Arguments in the form --<name>=<value>:"
|
||||||
|
<< "-o; --output <FILENAME.geojson> - File to write output to (Default: contours.geojson)\n"
|
||||||
|
<< "-i; --interval <int> - Set the interval between contours (Default: 5)\n"
|
||||||
|
<< "-b; --blur - Blur the image\n"
|
||||||
|
<< "--stats - Print statistical information about the heightmap\n"
|
||||||
|
<< "--steepness <filename> - Create steepness map\n";
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int interval;
|
||||||
|
cmdl({"-i", "--interval"}, 5) >> interval;
|
||||||
|
if (interval <= 0)
|
||||||
|
std::cerr << "Interval must be valid positive integer!" << "\n";
|
||||||
|
|
||||||
auto cellmap = produce_cellmap(&map, 40);
|
std::string output_file;
|
||||||
|
cmdl({"-o", "--output"}, "contours.geojson") >> output_file;
|
||||||
|
|
||||||
|
std::string input_file;
|
||||||
|
cmdl(1) >> input_file;
|
||||||
|
|
||||||
auto cellmaps = vector_cellmap(&map, 5);
|
HeightMap map(input_file.c_str());
|
||||||
//write_output_file(cellmaps, "out.geojson");
|
if (cmdl[{"-b", "--blur"}])
|
||||||
|
map.blur(0.8);
|
||||||
|
|
||||||
|
if (cmdl[{"--stats"}])
|
||||||
|
map.statistics();
|
||||||
|
|
||||||
|
std::string steepness_output_file;
|
||||||
|
//if (cmdl({"--steepness"}, "steepness.tif") >> steepness_output_file)
|
||||||
|
// map.calculate_steepness(output_file.c_str());
|
||||||
|
|
||||||
|
auto lines = create_lines(&map, interval);
|
||||||
|
write_output_file(lines, output_file.c_str(), &map);
|
||||||
|
std::cout << "Contours written to " << output_file << " 🗺️\n";
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user