mirror of
https://codeberg.org/comaps/comaps
synced 2026-01-13 07:34:31 +00:00
Compare commits
30 Commits
generate-2
...
zy-pano-bu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47fe00c76e | ||
|
|
b919186c9d | ||
|
|
f989e45f53 | ||
|
|
c0373e4557 | ||
|
|
5fb56d9abd | ||
|
|
b2eae1aa24 | ||
|
|
3f89c75b87 | ||
|
|
3e224c666b | ||
|
|
b9e7b9b994 | ||
|
|
e7ad0f20b6 | ||
|
|
401c4324d3 | ||
|
|
e72400760c | ||
|
|
a1e6cf9c60 | ||
|
|
37846bc6b1 | ||
|
|
e086920086 | ||
|
|
838fa4a288 | ||
|
|
c305c2e580 | ||
|
|
fccda2d8b9 | ||
|
|
75439b5d89 | ||
|
|
4a64bf05be | ||
|
|
381c1e3979 | ||
|
|
aa9ee3cbbf | ||
|
|
083a364d4a | ||
|
|
9a16e3f69c | ||
|
|
9e45e04d03 | ||
|
|
e9406c0f36 | ||
|
|
4d862b0a8b | ||
|
|
01cdc24512 | ||
|
|
7722cc7d46 | ||
|
|
5eeeaeb288 |
@@ -34,28 +34,155 @@ on:
|
||||
- track_generator/**
|
||||
- xcode/**
|
||||
|
||||
env:
|
||||
APT_PACKAGES: >-
|
||||
cmake
|
||||
ninja-build
|
||||
qt6-base-dev
|
||||
qt6-declarative-dev
|
||||
qt6-positioning-dev
|
||||
libqt6svg6-dev
|
||||
optipng
|
||||
libfreetype-dev
|
||||
libharfbuzz-dev
|
||||
libxrandr-dev
|
||||
libxinerama-dev
|
||||
libxcursor-dev
|
||||
libxi-dev
|
||||
python3-pip
|
||||
zlib1g-dev
|
||||
git
|
||||
ccache
|
||||
openjdk-21-jdk
|
||||
PIP_PACKAGES: protobuf<3.21
|
||||
SUBMODULE_CACHE_PATHS: |
|
||||
.git/modules
|
||||
3party/CMake-MetalShaderSupport
|
||||
3party/Vulkan-Headers
|
||||
3party/boost
|
||||
3party/expat
|
||||
3party/fast_double_parser
|
||||
3party/fast_obj
|
||||
3party/freetype/freetype
|
||||
3party/gflags
|
||||
3party/glaze
|
||||
3party/glfw
|
||||
3party/glm
|
||||
3party/googletest
|
||||
3party/harfbuzz/harfbuzz
|
||||
3party/icu/icu
|
||||
3party/imgui/imgui
|
||||
3party/jansson/jansson
|
||||
3party/just_gtfs
|
||||
3party/protobuf/protobuf
|
||||
3party/pugixml/pugixml
|
||||
3party/utfcpp
|
||||
tools/kothic
|
||||
tools/osmctools
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Android Lint
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: codeberg.org/comaps/docker-android-sdk:latest
|
||||
options: --user root
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/jvm/java-21-openjdk-amd64
|
||||
steps:
|
||||
- name: Get date for cache key
|
||||
id: date
|
||||
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create sudo wrapper
|
||||
shell: bash
|
||||
run: |
|
||||
echo '#!/bin/bash' > /usr/local/bin/sudo
|
||||
echo 'exec "$@"' >> /usr/local/bin/sudo
|
||||
chmod +x /usr/local/bin/sudo
|
||||
|
||||
- name: Apt update
|
||||
shell: bash
|
||||
run: |
|
||||
apt update -y
|
||||
|
||||
- name: Install and cache apt packages
|
||||
uses: https://github.com/awalsh128/cache-apt-pkgs-action@v1.6.0
|
||||
with:
|
||||
packages: ${{ env.APT_PACKAGES }}
|
||||
version: "${{ steps.date.outputs.date }}"
|
||||
|
||||
- name: Fallback manual apt install
|
||||
shell: bash
|
||||
run: |
|
||||
if ! command -v pip &> /dev/null; then
|
||||
echo "pip not found, cache action failed, installing packages manually"
|
||||
apt install -y $APT_PACKAGES
|
||||
fi
|
||||
|
||||
- name: Generate pip cache key
|
||||
id: pip-cache-key
|
||||
run: echo "key=pip-$(echo '${{ env.PIP_PACKAGES }}' | md5sum | cut -d' ' -f1)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache pip packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ steps.pip-cache-key.outputs.key }}
|
||||
|
||||
- name: Install build dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
pip install "${{ env.PIP_PACKAGES }}" --break-system-packages
|
||||
update-alternatives --set java /usr/lib/jvm/java-21-openjdk-amd64/bin/java
|
||||
|
||||
- name: Install Android SDK components
|
||||
shell: bash
|
||||
run: |
|
||||
yes | sdkmanager --licenses || true
|
||||
sdkmanager "platforms;android-36" "build-tools;36.0.0" "ndk;28.2.13676358"
|
||||
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Parallel submodules checkout
|
||||
- name: Cache world map
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: world_mwm
|
||||
key: world-mwm
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.SUBMODULE_CACHE_PATHS }}
|
||||
key: submodules-${{ hashFiles('.gitmodules') }}
|
||||
restore-keys: |
|
||||
submodules-
|
||||
|
||||
- name: Sync and update submodules
|
||||
shell: bash
|
||||
run: git submodule update --depth 1 --init --recursive --jobs=$(($(nproc) * 20))
|
||||
run: |
|
||||
git submodule sync --recursive
|
||||
git submodule update --depth 1 --init --recursive --jobs=$(($(nproc) * 20))
|
||||
|
||||
- name: Lint
|
||||
shell: bash
|
||||
working-directory: android
|
||||
run: ./gradlew -Pandroidauto=true lint
|
||||
run: |
|
||||
cd ..
|
||||
./configure.sh
|
||||
cd android
|
||||
./gradlew -Pandroidauto=true lint
|
||||
|
||||
android-check:
|
||||
name: Build Android Debug
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: codeberg.org/comaps/docker-android-sdk:latest
|
||||
options: --user root
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/jvm/java-21-openjdk-amd64
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -71,23 +198,84 @@ jobs:
|
||||
cancel-in-progress: true
|
||||
|
||||
steps:
|
||||
- name: Install build tools and dependencies
|
||||
- name: Get date for cache key
|
||||
id: date
|
||||
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create sudo wrapper
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y ninja-build
|
||||
echo '#!/bin/bash' > /usr/local/bin/sudo
|
||||
echo 'exec "$@"' >> /usr/local/bin/sudo
|
||||
chmod +x /usr/local/bin/sudo
|
||||
|
||||
- name: Apt update
|
||||
shell: bash
|
||||
run: |
|
||||
apt update -y
|
||||
|
||||
- name: Install and cache apt packages
|
||||
uses: https://github.com/awalsh128/cache-apt-pkgs-action@v1.6.0
|
||||
with:
|
||||
packages: ${{ env.APT_PACKAGES }}
|
||||
version: "${{ steps.date.outputs.date }}"
|
||||
|
||||
- name: Fallback manual apt install
|
||||
shell: bash
|
||||
run: |
|
||||
if ! command -v pip &> /dev/null; then
|
||||
echo "pip not found, cache action failed, installing packages manually"
|
||||
apt install -y $APT_PACKAGES
|
||||
fi
|
||||
|
||||
- name: Generate pip cache key
|
||||
id: pip-cache-key
|
||||
run: echo "key=pip-$(echo '${{ env.PIP_PACKAGES }}' | md5sum | cut -d' ' -f1)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache pip packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ steps.pip-cache-key.outputs.key }}
|
||||
|
||||
- name: Install build dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
pip install "${{ env.PIP_PACKAGES }}" --break-system-packages
|
||||
update-alternatives --set java /usr/lib/jvm/java-21-openjdk-amd64/bin/java
|
||||
|
||||
- name: Install Android SDK components
|
||||
shell: bash
|
||||
run: |
|
||||
yes | sdkmanager --licenses || true
|
||||
sdkmanager "platforms;android-36" "build-tools;36.0.0" "ndk;28.2.13676358"
|
||||
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 200 # enough to get all commits for the current day
|
||||
|
||||
- name: Parallel submodules checkout
|
||||
- name: Cache world map
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: world_mwm
|
||||
key: world-mwm
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.SUBMODULE_CACHE_PATHS }}
|
||||
key: submodules-${{ hashFiles('.gitmodules') }}
|
||||
restore-keys: |
|
||||
submodules-
|
||||
|
||||
- name: Sync and update submodules
|
||||
shell: bash
|
||||
run: git submodule update --depth 1 --init --recursive --jobs=$(($(nproc) * 20))
|
||||
run: |
|
||||
git submodule sync --recursive
|
||||
git submodule update --depth 1 --init --recursive --jobs=$(($(nproc) * 20))
|
||||
|
||||
- name: Configure ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
uses: https://github.com/hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: ${{ github.workflow }}-${{ matrix.flavor }}
|
||||
|
||||
@@ -98,13 +286,22 @@ jobs:
|
||||
CMAKE_C_COMPILER_LAUNCHER: ccache
|
||||
CMAKE_CXX_COMPILER_LAUNCHER: ccache
|
||||
run: |
|
||||
cmake --version
|
||||
ninja --version
|
||||
cd ..
|
||||
./configure.sh
|
||||
cd android
|
||||
./gradlew -P${{ matrix.arch }} assemble${{ matrix.flavor }}
|
||||
|
||||
- name: Find built APK
|
||||
id: find-apk
|
||||
shell: bash
|
||||
run: |
|
||||
APK_PATH=$(find android/app/build/outputs/apk/**/ -name "*.apk" -type f | head -n 1)
|
||||
echo "apk_path=$APK_PATH" >> $GITHUB_OUTPUT
|
||||
echo "Found APK: $APK_PATH"
|
||||
|
||||
- name: Upload ${{ matrix.flavor }} apk
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: android-${{ matrix.flavor }}
|
||||
path: android/app/build/outputs/apk/**/OrganicMaps-*.apk
|
||||
path: ${{ steps.find-apk.outputs.apk_path }}
|
||||
if-no-files-found: error
|
||||
|
||||
@@ -17,12 +17,22 @@ on:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
run-panoramax:
|
||||
description: 'Update Panoramax imagery?'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
# run-cleanup:
|
||||
# description: 'Clean up old build files?'
|
||||
# required: false
|
||||
# default: true
|
||||
# type: boolean
|
||||
run-tiger:
|
||||
description: 'Update TIGER address data?'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
run-planet-pbf:
|
||||
run-planet:
|
||||
description: 'Update PBF planet (for Wiki & subways)?'
|
||||
required: false
|
||||
default: true
|
||||
@@ -37,11 +47,11 @@ on:
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
run-planet-o5m:
|
||||
description: 'Update O5M planet (for mapgen)?'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
# run-planet-o5m:
|
||||
# description: 'Update O5M planet (for mapgen)?'
|
||||
# required: false
|
||||
# default: true
|
||||
# type: boolean
|
||||
run-mapgen:
|
||||
description: 'Run maps generation?'
|
||||
required: false
|
||||
@@ -80,11 +90,54 @@ env:
|
||||
ZULIP_API_KEY: ${{ secrets.ZULIP_API_KEY }}
|
||||
MWMTEST: ${{ inputs.map-generator-test }}
|
||||
MWMCONTINUE: ${{ inputs.map-generator-continue }}
|
||||
# MWMCOUNTRIES: ${{ inputs.map-generator-countries }}
|
||||
#TODO: undo inputs.map-generator-countries
|
||||
MWMCOUNTRIES: US_Oregon_Portland
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
TZ: Etc/UTC
|
||||
|
||||
jobs:
|
||||
cleanup-old-files:
|
||||
# if: inputs.run-cleanup
|
||||
name: Clean Up Old Files
|
||||
runs-on: mapfilemaker
|
||||
container:
|
||||
image: codeberg.org/comaps/maps_generator:f6d53d54f794
|
||||
volumes:
|
||||
- /mnt/4tbexternal/:/mnt/4tbexternal/
|
||||
- /mnt/4tbexternal/osm-planet:/home/planet
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-map-generator-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Remove intermediate data
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Checking for intermediate map build data in /mnt/4tbexternal/osm-maps..."
|
||||
cd /mnt/4tbexternal/osm-maps/
|
||||
# List all dated directories, sort by name (newest first)
|
||||
ls -1d */ 2>/dev/null | grep -E '^[0-9]{4}_[0-9]{2}_[0-9]{2}__[0-9]{2}_[0-9]{2}_[0-9]{2}/$' | while read dir; do
|
||||
if [ -d "$dir/intermediate_data" ]; then
|
||||
echo "Removing $dir/intermediate_data"
|
||||
fi
|
||||
if [ -d "$dir/osm2ft" ]; then
|
||||
echo "Removing $dir/osm2ft"
|
||||
fi
|
||||
if [ -f "$dir/world_roads.o5m" ]; then
|
||||
echo "Removing $dir/world_roads.o5m"
|
||||
fi
|
||||
done
|
||||
echo "Intermediate data cleaned up."
|
||||
- name: Remove old map builds (keep last 6)
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Checking for old map builds in /mnt/4tbexternal/osm-maps..."
|
||||
cd /mnt/4tbexternal/osm-maps/
|
||||
# List all dated directories, sort by name (newest first), skip first 6, delete the rest
|
||||
ls -1d */ 2>/dev/null | grep -E '^[0-9]{4}_[0-9]{2}_[0-9]{2}__[0-9]{2}_[0-9]{2}_[0-9]{2}/$' | sort -r | tail -n +7 | while read dir; do
|
||||
echo "Removing old build: $dir"
|
||||
rm -rf "$dir"
|
||||
done
|
||||
echo "Old map builds cleaned up."
|
||||
clone-repos:
|
||||
name: Clone Git Repos
|
||||
runs-on: mapfilemaker
|
||||
@@ -209,6 +262,130 @@ jobs:
|
||||
--data-urlencode topic=codeberg-bot \
|
||||
--data-urlencode 'content=Isolines are done!'
|
||||
|
||||
update-panoramax:
|
||||
if: inputs.run-panoramax
|
||||
name: Update Panoramax
|
||||
runs-on: mapfilemaker
|
||||
needs:
|
||||
- clone-repos
|
||||
container:
|
||||
image: codeberg.org/comaps/maps_generator:f6d53d54f794
|
||||
volumes:
|
||||
- /mnt/4tbexternal/:/mnt/4tbexternal/
|
||||
- /mnt/4tbexternal/osm-planet:/home/planet
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-map-generator-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: "~"
|
||||
key: cache-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
- name: Install Python dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
pip install pyarrow duckdb shapely
|
||||
- name: Download Panoramax Geoparquet
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p /home/planet/panoramax
|
||||
cd /home/planet/panoramax
|
||||
|
||||
PARQUET_UPDATED=false
|
||||
|
||||
# Download the global Panoramax geoparquet file (20GB)
|
||||
if [ ! -f panoramax.parquet ]; then
|
||||
echo "panoramax.parquet does not exist, will download"
|
||||
PARQUET_UPDATED=true
|
||||
else
|
||||
# Check if file is older than 7 days
|
||||
FILE_AGE_DAYS=$(( ($(date +%s) - $(stat -c %Y panoramax.parquet)) / 86400 ))
|
||||
echo "panoramax.parquet is $FILE_AGE_DAYS days old"
|
||||
|
||||
if [ $FILE_AGE_DAYS -gt 7 ]; then
|
||||
echo "File is older than 7 days, will re-download"
|
||||
PARQUET_UPDATED=true
|
||||
else
|
||||
echo "File is recent (< 7 days), skipping download"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$PARQUET_UPDATED" = "true" ]; then
|
||||
echo "Downloading Panoramax geoparquet..."
|
||||
curl -L -o panoramax.parquet.tmp https://api.panoramax.xyz/data/geoparquet/panoramax.parquet
|
||||
mv panoramax.parquet.tmp panoramax.parquet
|
||||
fi
|
||||
|
||||
# Export to GitHub environment for next step
|
||||
echo "PARQUET_UPDATED=$PARQUET_UPDATED" >> $GITHUB_ENV
|
||||
- name: Process Panoramax to per-country files
|
||||
shell: bash
|
||||
run: |
|
||||
cd ~/comaps
|
||||
mkdir -p /home/planet/panoramax/countries
|
||||
|
||||
SHOULD_PROCESS=false
|
||||
|
||||
# Check if parquet was just updated in this workflow run
|
||||
if [ "$PARQUET_UPDATED" = "true" ]; then
|
||||
echo "Parquet file was just updated, will process"
|
||||
SHOULD_PROCESS=true
|
||||
# Check if country files don't exist
|
||||
elif [ ! "$(ls -A /home/planet/panoramax/countries/*.panoramax 2>/dev/null)" ]; then
|
||||
echo "No country files exist, will process"
|
||||
SHOULD_PROCESS=true
|
||||
# Check if planet file is newer than last processing marker
|
||||
elif [ -f /home/planet/planet/planet.o5m ] && [ -f /home/planet/panoramax/countries/.last_processed ]; then
|
||||
if [ /home/planet/planet/planet.o5m -nt /home/planet/panoramax/countries/.last_processed ]; then
|
||||
echo "Planet file is newer than last processing, will process"
|
||||
SHOULD_PROCESS=true
|
||||
else
|
||||
echo "Country files are up-to-date, skipping processing"
|
||||
fi
|
||||
elif [ -f /home/planet/planet/planet.o5m ]; then
|
||||
echo "No processing marker exists but planet file does, will process"
|
||||
SHOULD_PROCESS=true
|
||||
else
|
||||
echo "Country files are up-to-date, skipping processing"
|
||||
fi
|
||||
|
||||
if [ "$SHOULD_PROCESS" = "true" ]; then
|
||||
echo "Processing panoramax data to per-country files..."
|
||||
python3 tools/python/maps_generator/panoramax_preprocessor.py \
|
||||
--input /home/planet/panoramax/panoramax.parquet \
|
||||
--output /home/planet/panoramax/countries \
|
||||
--borders-dir ~/comaps/data/borders
|
||||
|
||||
# Mark when processing completed (persists in /home/planet for timestamp comparison)
|
||||
touch /home/planet/panoramax/countries/.last_processed
|
||||
fi
|
||||
|
||||
# Export to GitHub environment for notification step
|
||||
echo "PANORAMAX_PROCESSED=$SHOULD_PROCESS" >> $GITHUB_ENV
|
||||
- name: Check panoramax files
|
||||
shell: bash
|
||||
run: |
|
||||
NUMPANO=$(ls -1 /home/planet/panoramax/countries/*.panoramax 2>/dev/null | wc -l)
|
||||
echo "Found $NUMPANO panoramax country files"
|
||||
if [ $NUMPANO -lt 5 ]; then
|
||||
echo "ERROR: Did generation fail? Expected at least 5 country files"
|
||||
exit 1
|
||||
fi
|
||||
- name: Notify Zulip
|
||||
shell: bash
|
||||
run: |
|
||||
# Only notify if processing actually happened in this workflow run
|
||||
if [ "$PANORAMAX_PROCESSED" = "true" ]; then
|
||||
curl -X POST https://comaps.zulipchat.com/api/v1/messages \
|
||||
-u $ZULIP_BOT_EMAIL:$ZULIP_API_KEY \
|
||||
--data-urlencode type=stream \
|
||||
--data-urlencode 'to="DevOps"' \
|
||||
--data-urlencode topic=codeberg-bot \
|
||||
--data-urlencode 'content=Panoramax processing is done!'
|
||||
else
|
||||
echo "No processing occurred in this run, skipping notification"
|
||||
fi
|
||||
|
||||
update-tiger:
|
||||
if: inputs.run-tiger
|
||||
name: Update TIGER
|
||||
@@ -247,7 +424,7 @@ jobs:
|
||||
tar -xOzf /home/planet/tiger-nominatim-preprocessed-latest.csv.tar.gz | ~/omim-build-relwithdebinfo/address_parser_tool --output_path=/home/planet/tiger
|
||||
|
||||
update-planet-pbf:
|
||||
if: inputs.run-planet-pbf
|
||||
if: inputs.run-planet
|
||||
name: Update PBF Planet
|
||||
runs-on: mapfilemaker
|
||||
container:
|
||||
@@ -431,7 +608,7 @@ jobs:
|
||||
--data-urlencode 'content=Wiki update is done!'
|
||||
|
||||
update-planet-o5m:
|
||||
if: inputs.run-planet-o5m
|
||||
if: inputs.run-planet
|
||||
name: Update O5M Planet
|
||||
runs-on: mapfilemaker
|
||||
container:
|
||||
@@ -574,4 +751,3 @@ jobs:
|
||||
--data-urlencode 'to="DevOps"' \
|
||||
--data-urlencode topic=codeberg-bot \
|
||||
--data-urlencode 'content=Upload is done!'
|
||||
|
||||
|
||||
@@ -74,6 +74,11 @@ public class PlacePageButtonFactory
|
||||
titleId = R.string.avoid_ferry;
|
||||
yield R.drawable.ic_avoid_ferry;
|
||||
}
|
||||
case PANORAMAX ->
|
||||
{
|
||||
titleId = R.string.panoramax;
|
||||
yield R.drawable.ic_camera;
|
||||
}
|
||||
case MORE ->
|
||||
{
|
||||
titleId = R.string.placepage_more_button;
|
||||
|
||||
@@ -144,6 +144,7 @@ public final class PlacePageButtons extends Fragment implements Observer<List<Pl
|
||||
ROUTE_AVOID_TOLL,
|
||||
ROUTE_AVOID_FERRY,
|
||||
ROUTE_AVOID_UNPAVED,
|
||||
PANORAMAX,
|
||||
MORE
|
||||
}
|
||||
|
||||
|
||||
@@ -428,6 +428,7 @@ public class PlacePageController
|
||||
case ROUTE_AVOID_TOLL -> onAvoidTollBtnClicked();
|
||||
case ROUTE_AVOID_UNPAVED -> onAvoidUnpavedBtnClicked();
|
||||
case ROUTE_AVOID_FERRY -> onAvoidFerryBtnClicked();
|
||||
case PANORAMAX -> onPanoramaxBtnClicked();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,6 +500,19 @@ public class PlacePageController
|
||||
requireActivity().finish();
|
||||
}
|
||||
|
||||
private void onPanoramaxBtnClicked()
|
||||
{
|
||||
if (mMapObject == null)
|
||||
return;
|
||||
String url = Framework.nativeGetPanoramaxUrl();
|
||||
if (!TextUtils.isEmpty(url))
|
||||
{
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(android.net.Uri.parse(url));
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
private void onRouteFromBtnClicked()
|
||||
{
|
||||
if (mMapObject == null)
|
||||
@@ -637,6 +651,10 @@ public class PlacePageController
|
||||
buttons.add(mapObject.isBookmark() ? PlacePageButtons.ButtonType.BOOKMARK_DELETE
|
||||
: PlacePageButtons.ButtonType.BOOKMARK_SAVE);
|
||||
}
|
||||
|
||||
// Add Panoramax button if imagery is available
|
||||
if (Framework.nativeHasPanoramax())
|
||||
buttons.add(PlacePageButtons.ButtonType.PANORAMAX);
|
||||
}
|
||||
mViewModel.setCurrentButtons(buttons);
|
||||
}
|
||||
|
||||
@@ -1764,6 +1764,16 @@ JNIEXPORT jboolean JNICALL Java_app_organicmaps_sdk_Framework_nativeHasPlacePage
|
||||
return static_cast<jboolean>(frm()->HasPlacePageInfo());
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_app_organicmaps_sdk_Framework_nativeHasPanoramax(JNIEnv *, jclass)
|
||||
{
|
||||
return static_cast<jboolean>(g_framework->GetPlacePageInfo().HasPanoramax());
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_app_organicmaps_sdk_Framework_nativeGetPanoramaxUrl(JNIEnv * env, jclass)
|
||||
{
|
||||
return jni::ToJavaString(env, g_framework->GetPlacePageInfo().GetPanoramaxUrl());
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_app_organicmaps_sdk_Framework_nativeMemoryWarning(JNIEnv *, jclass)
|
||||
{
|
||||
return frm()->MemoryWarning();
|
||||
|
||||
@@ -349,6 +349,8 @@ public class Framework
|
||||
* @return true if c++ framework has initialized internal place page object, otherwise - false.
|
||||
*/
|
||||
public static native boolean nativeHasPlacePageInfo();
|
||||
public static native boolean nativeHasPanoramax();
|
||||
public static native String nativeGetPanoramaxUrl();
|
||||
|
||||
public static native void nativeMemoryWarning();
|
||||
public static native void nativeSaveRoute();
|
||||
|
||||
@@ -1424,6 +1424,7 @@
|
||||
<string name="type.tourism.information.board">Information Board</string>
|
||||
<string name="type.tourism.information.guidepost">Guidepost</string>
|
||||
<string name="type.tourism.information.map">Tourist Map</string>
|
||||
<string name="type.tourism.information.tactile_map">Tactile Map</string>
|
||||
<string name="type.tourism.information.office">Tourist Office</string>
|
||||
<string name="type.tourism.information.visitor_centre">Visitor Centre</string>
|
||||
<string name="type.amenity.ranger_station">Ranger Station</string>
|
||||
|
||||
@@ -402,6 +402,7 @@
|
||||
"tourism-chalet|@category_hotel": "5Holiday Cottage|5vacation home|4Chalet",
|
||||
"tourism-information-board": "Information Board",
|
||||
"tourism-information-map": "Tourist map|map|4Information",
|
||||
"tourism-information-tactile_map": "Tactile map|map|4Information",
|
||||
"tourism-information-guidepost": "Guidepost",
|
||||
"aerialway-station": "Aerialway Station|5Cable car station",
|
||||
"aeroway-helipad": "4Helipad",
|
||||
|
||||
@@ -518,7 +518,7 @@ piste:type|sled;402;
|
||||
leisure|beach_resort;403;
|
||||
leisure|dog_park;404;
|
||||
aerialway|gondola;405;
|
||||
deprecated:historic|museum:10.2021;[historic=museum];x;name;int_name;406;tourism|museum
|
||||
tourism|information|tactile_map;[tourism=information][information=tactile_map];;name;int_name;406;
|
||||
highway|living_street|bridge;[highway=living_street][bridge?];;name;int_name;407;
|
||||
leisure|track|area;[leisure=track][area?];;name;int_name;408;
|
||||
railway|monorail;409;
|
||||
@@ -1758,3 +1758,4 @@ amenity|luggage_locker;1629;
|
||||
building|guardhouse;[building=guardhouse],[amenity=security_booth],[amenity=checkpoint];;;;1630;
|
||||
office|security;1631;
|
||||
shop|lighting;1632;
|
||||
panoramax|image;1633;
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 16.
|
@@ -766,7 +766,8 @@ node|z16-[tourism=information][information=board]
|
||||
{icon-image: board.svg;text-offset: 1;icon-min-distance: 10;}
|
||||
node|z16-[tourism=information][information=guidepost]
|
||||
{icon-image: guidepost.svg;text-offset: 1;icon-min-distance: 10;}
|
||||
node|z16-[tourism=information][information=map]
|
||||
node|z16-[tourism=information][information=map],
|
||||
node|z16-[tourism=information][information=tactile_map],
|
||||
{icon-image: map.svg;text-offset: 1;icon-min-distance: 10;}
|
||||
|
||||
node|z15-[amenity=ranger_station],
|
||||
|
||||
@@ -2387,6 +2387,7 @@ tourism-information # icon z16- (also has captio
|
||||
tourism-information-board # icon z16- (also has caption(optional) z16-)
|
||||
tourism-information-guidepost # icon z16- (also has caption(optional) z16-)
|
||||
tourism-information-map # icon z16- (also has caption(optional) z16-)
|
||||
tourism-information-tactile_map # icon z16- (also has caption(optional) z16-)
|
||||
=== -9940
|
||||
|
||||
amenity # caption z19-
|
||||
@@ -2478,4 +2479,5 @@ entrance-service # icon z19- (also has captio
|
||||
# tourism-information-board # caption(optional) z16- (also has icon z16-)
|
||||
# tourism-information-guidepost # caption(optional) z16- (also has icon z16-)
|
||||
# tourism-information-map # caption(optional) z16- (also has icon z16-)
|
||||
# tourism-information-tactile_map # caption(optional) z16- (also has icon z16-)
|
||||
=== -10000
|
||||
|
||||
@@ -353,6 +353,7 @@ node|z15-[tourism=information][information=board],
|
||||
node|z15-[tourism=information][information=guidepost],
|
||||
{icon-image: guidepost.svg;}
|
||||
node|z15-[tourism=information][information=map],
|
||||
node|z15-[tourism=information][information=tactile_map],
|
||||
{icon-image: map.svg;}
|
||||
|
||||
node|z13-[amenity=ranger_station],
|
||||
|
||||
@@ -2393,6 +2393,7 @@ tourism-information # icon z15- (also has captio
|
||||
tourism-information-board # icon z15- (also has caption(optional) z15-)
|
||||
tourism-information-guidepost # icon z15- (also has caption(optional) z15-)
|
||||
tourism-information-map # icon z15- (also has caption(optional) z15-)
|
||||
tourism-information-tactile_map # icon z15- (also has caption(optional) z15-)
|
||||
=== -9940
|
||||
|
||||
amenity # caption z19-
|
||||
@@ -2484,4 +2485,5 @@ entrance-service # icon z19- (also has captio
|
||||
# tourism-information-board # caption(optional) z15- (also has icon z15-)
|
||||
# tourism-information-guidepost # caption(optional) z15- (also has icon z15-)
|
||||
# tourism-information-map # caption(optional) z15- (also has icon z15-)
|
||||
# tourism-information-tactile_map # caption(optional) z15- (also has icon z15-)
|
||||
=== -10000
|
||||
|
||||
@@ -147,6 +147,8 @@ set(SRC
|
||||
osm_o5m_source.hpp
|
||||
osm_source.cpp
|
||||
osm_xml_source.hpp
|
||||
panoramax_generator.cpp
|
||||
panoramax_generator.hpp
|
||||
place_processor.cpp
|
||||
place_processor.hpp
|
||||
platform_helpers.cpp
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "generator/feature_builder.hpp"
|
||||
#include "generator/final_processor_utils.hpp"
|
||||
#include "generator/isolines_generator.hpp"
|
||||
#include "generator/panoramax_generator.hpp"
|
||||
#include "generator/mini_roundabout_transformer.hpp"
|
||||
#include "generator/node_mixer.hpp"
|
||||
#include "generator/osm2type.hpp"
|
||||
@@ -68,6 +69,10 @@ void CountryFinalProcessor::Process()
|
||||
if (!m_isolinesPath.empty())
|
||||
AddIsolines();
|
||||
|
||||
LOG(LINFO, ("Adding panoramax..."));
|
||||
if (!m_panoramaxPath.empty())
|
||||
AddPanoramax();
|
||||
|
||||
// DropProhibitedSpeedCameras();
|
||||
LOG(LINFO, ("Processing building parts..."));
|
||||
ProcessBuildingParts();
|
||||
@@ -293,6 +298,22 @@ void CountryFinalProcessor::AddAddresses()
|
||||
LOG(LINFO, ("Total addresses:", totalStats));
|
||||
}
|
||||
|
||||
void CountryFinalProcessor::AddPanoramax()
|
||||
{
|
||||
if (m_panoramaxPath.empty())
|
||||
return;
|
||||
|
||||
PanoramaxFeaturesGenerator panoramaxGenerator(m_panoramaxPath);
|
||||
ForEachMwmTmp(m_temporaryMwmPath, [&](auto const & name, auto const & path)
|
||||
{
|
||||
if (!IsCountry(name))
|
||||
return;
|
||||
|
||||
FeatureBuilderWriter<serialization_policy::MaxAccuracy> writer(path, FileWriter::Op::OP_APPEND);
|
||||
panoramaxGenerator.GeneratePanoramax(name, [&](auto const & fb) { writer.Write(fb); });
|
||||
}, m_threadsCount);
|
||||
}
|
||||
|
||||
void CountryFinalProcessor::ProcessCoastline()
|
||||
{
|
||||
/// @todo We can remove MinSize at all.
|
||||
|
||||
@@ -24,6 +24,7 @@ public:
|
||||
|
||||
void SetIsolinesDir(std::string const & dir) { m_isolinesPath = dir; }
|
||||
void SetAddressesDir(std::string const & dir) { m_addressPath = dir; }
|
||||
void SetPanoramaxDir(std::string const & dir) { m_panoramaxPath = dir; }
|
||||
|
||||
void SetCityBoundariesFiles(std::string const & collectorFile) { m_boundariesCollectorFile = collectorFile; }
|
||||
|
||||
@@ -39,6 +40,7 @@ private:
|
||||
void AddFakeNodes();
|
||||
void AddIsolines();
|
||||
void AddAddresses();
|
||||
void AddPanoramax();
|
||||
void DropProhibitedSpeedCameras();
|
||||
// void Finish();
|
||||
|
||||
@@ -47,7 +49,7 @@ private:
|
||||
std::string m_borderPath;
|
||||
std::string m_temporaryMwmPath;
|
||||
std::string m_intermediateDir;
|
||||
std::string m_isolinesPath, m_addressPath;
|
||||
std::string m_isolinesPath, m_addressPath, m_panoramaxPath;
|
||||
std::string m_boundariesCollectorFile;
|
||||
std::string m_coastlineGeomFilename;
|
||||
std::string m_worldCoastsFilename;
|
||||
|
||||
@@ -39,8 +39,8 @@ struct GenerateInfo
|
||||
|
||||
std::string m_cacheDir;
|
||||
|
||||
// External folders with additional preprocessed data (isolines, addresses).
|
||||
std::string m_isolinesDir, m_addressesDir;
|
||||
// External folders with additional preprocessed data (isolines, addresses, panoramax).
|
||||
std::string m_isolinesDir, m_addressesDir, m_panoramaxDir;
|
||||
|
||||
// Current generated file name if --output option is defined.
|
||||
std::string m_fileName;
|
||||
|
||||
@@ -2979,6 +2979,7 @@ UNIT_CLASS_TEST(TestWithClassificator, OsmType_ComplexTypesSmoke)
|
||||
{{"tourism", "information", "board"}, {{"tourism", "information"}, {"information", "board"}}},
|
||||
{{"tourism", "information", "guidepost"}, {{"tourism", "information"}, {"information", "guidepost"}}},
|
||||
{{"tourism", "information", "map"}, {{"tourism", "information"}, {"information", "map"}}},
|
||||
{{"tourism", "information", "tactile_map"}, {{"tourism", "information"}, {"information", "tactile_map"}}},
|
||||
{{"tourism", "information", "office"}, {{"tourism", "information"}, {"information", "office"}}},
|
||||
//{{"waterway", "canal", "tunnel"}, {{"waterway", "canal"}, {"tunnel", "any_value"}}},
|
||||
//{{"waterway", "river", "tunnel"}, {{"waterway", "river"}, {"tunnel", "any_value"}}},
|
||||
|
||||
@@ -107,6 +107,7 @@ DEFINE_string(nodes_list_path, "",
|
||||
DEFINE_bool(generate_isolines_info, false, "Generate the isolines info section");
|
||||
DEFINE_string(isolines_path, "", "Path to isolines directory. If set, adds isolines linear features.");
|
||||
DEFINE_string(addresses_path, "", "Path to addresses directory. If set, adds addr:interpolation features.");
|
||||
DEFINE_string(panoramax_path, "", "Path to panoramax directory. If set, adds panoramax imagery point features.");
|
||||
|
||||
// Routing.
|
||||
DEFINE_bool(make_routing_index, false, "Make sections with the routing information.");
|
||||
@@ -243,6 +244,7 @@ MAIN_WITH_ERROR_HANDLING([](int argc, char ** argv)
|
||||
genInfo.m_complexHierarchyFilename = FLAGS_complex_hierarchy_data;
|
||||
genInfo.m_isolinesDir = FLAGS_isolines_path;
|
||||
genInfo.m_addressesDir = FLAGS_addresses_path;
|
||||
genInfo.m_panoramaxDir = FLAGS_panoramax_path;
|
||||
|
||||
// Use merged style.
|
||||
GetStyleReader().SetCurrentStyle(MapStyleMerged);
|
||||
|
||||
141
generator/panoramax_generator.cpp
Normal file
141
generator/panoramax_generator.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
#include "generator/panoramax_generator.hpp"
|
||||
|
||||
#include "indexer/classificator.hpp"
|
||||
#include "indexer/feature_meta.hpp"
|
||||
|
||||
#include "coding/file_reader.hpp"
|
||||
#include "coding/read_write_utils.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/logging.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
|
||||
namespace generator
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::string_view const kPanoramax = "panoramax";
|
||||
std::string_view const kImage = "image";
|
||||
|
||||
std::string GetPanoramaxFilePath(std::string const & countryName, std::string const & panoramaxDir)
|
||||
{
|
||||
return panoramaxDir + "/" + countryName + ".panoramax";
|
||||
}
|
||||
|
||||
struct PanoramaxPoint
|
||||
{
|
||||
double lat;
|
||||
double lon;
|
||||
std::string imageId;
|
||||
};
|
||||
|
||||
bool LoadPanoramaxPoints(std::string const & filePath, std::vector<PanoramaxPoint> & points)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::ifstream file(filePath, std::ios::binary);
|
||||
if (!file.is_open())
|
||||
{
|
||||
LOG(LWARNING, ("Can't open panoramax file", filePath));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read header
|
||||
uint32_t version;
|
||||
uint64_t pointCount;
|
||||
|
||||
file.read(reinterpret_cast<char*>(&version), sizeof(version));
|
||||
file.read(reinterpret_cast<char*>(&pointCount), sizeof(pointCount));
|
||||
|
||||
if (version != 1)
|
||||
{
|
||||
LOG(LERROR, ("Unsupported panoramax file version", version));
|
||||
return false;
|
||||
}
|
||||
|
||||
points.reserve(static_cast<size_t>(pointCount));
|
||||
|
||||
// Read points
|
||||
for (uint64_t i = 0; i < pointCount; ++i)
|
||||
{
|
||||
PanoramaxPoint point;
|
||||
|
||||
file.read(reinterpret_cast<char*>(&point.lat), sizeof(point.lat));
|
||||
file.read(reinterpret_cast<char*>(&point.lon), sizeof(point.lon));
|
||||
|
||||
// Read image_id (length-prefixed string)
|
||||
uint32_t imageIdLength;
|
||||
file.read(reinterpret_cast<char*>(&imageIdLength), sizeof(imageIdLength));
|
||||
|
||||
if (imageIdLength > 0 && imageIdLength < 10000) // Sanity check
|
||||
{
|
||||
point.imageId.resize(imageIdLength);
|
||||
file.read(&point.imageId[0], imageIdLength);
|
||||
}
|
||||
|
||||
if (file.fail())
|
||||
{
|
||||
LOG(LERROR, ("Error reading panoramax point", i, "from", filePath));
|
||||
return false;
|
||||
}
|
||||
|
||||
points.push_back(std::move(point));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (std::exception const & e)
|
||||
{
|
||||
LOG(LERROR, ("Exception loading panoramax file", filePath, ":", e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
PanoramaxFeaturesGenerator::PanoramaxFeaturesGenerator(std::string const & panoramaxDir)
|
||||
: m_panoramaxDir(panoramaxDir)
|
||||
{
|
||||
Classificator const & c = classif();
|
||||
m_panoramaxType = c.GetTypeByPath({kPanoramax, kImage});
|
||||
}
|
||||
|
||||
void PanoramaxFeaturesGenerator::GeneratePanoramax(std::string const & countryName,
|
||||
FeaturesCollectFn const & fn) const
|
||||
{
|
||||
auto const panoramaxPath = GetPanoramaxFilePath(countryName, m_panoramaxDir);
|
||||
|
||||
std::vector<PanoramaxPoint> points;
|
||||
if (!LoadPanoramaxPoints(panoramaxPath, points))
|
||||
{
|
||||
LOG(LWARNING, ("Can't load panoramax points for", countryName));
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(LINFO, ("Generating", points.size(), "panoramax points for", countryName));
|
||||
|
||||
for (auto const & point : points)
|
||||
{
|
||||
feature::FeatureBuilder fb;
|
||||
|
||||
// Set point geometry
|
||||
m2::PointD const mercatorPoint = mercator::FromLatLon(point.lat, point.lon);
|
||||
fb.SetCenter(mercatorPoint);
|
||||
|
||||
// Add classificator type
|
||||
fb.AddType(m_panoramaxType);
|
||||
|
||||
// Add metadata with image ID
|
||||
if (!point.imageId.empty())
|
||||
{
|
||||
fb.GetMetadata().Set(feature::Metadata::FMD_PANORAMAX, point.imageId);
|
||||
}
|
||||
|
||||
fn(std::move(fb));
|
||||
}
|
||||
}
|
||||
} // namespace generator
|
||||
24
generator/panoramax_generator.hpp
Normal file
24
generator/panoramax_generator.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "generator/feature_builder.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace generator
|
||||
{
|
||||
// Generates Panoramax imagery point features from binary files.
|
||||
// Binary files are created by the panoramax_preprocessor.py script.
|
||||
class PanoramaxFeaturesGenerator
|
||||
{
|
||||
public:
|
||||
explicit PanoramaxFeaturesGenerator(std::string const & panoramaxDir);
|
||||
|
||||
using FeaturesCollectFn = std::function<void(feature::FeatureBuilder && fb)>;
|
||||
void GeneratePanoramax(std::string const & countryName, FeaturesCollectFn const & fn) const;
|
||||
|
||||
private:
|
||||
std::string m_panoramaxDir;
|
||||
uint32_t m_panoramaxType; // Classificator type for panoramax|image
|
||||
};
|
||||
} // namespace generator
|
||||
@@ -182,6 +182,7 @@ RawGenerator::FinalProcessorPtr RawGenerator::CreateCountryFinalProcessor(Affili
|
||||
auto finalProcessor = std::make_shared<CountryFinalProcessor>(affiliations, m_genInfo.m_tmpDir, m_threadsCount);
|
||||
finalProcessor->SetIsolinesDir(m_genInfo.m_isolinesDir);
|
||||
finalProcessor->SetAddressesDir(m_genInfo.m_addressesDir);
|
||||
finalProcessor->SetPanoramaxDir(m_genInfo.m_panoramaxDir);
|
||||
finalProcessor->SetMiniRoundabouts(m_genInfo.GetIntermediateFileName(MINI_ROUNDABOUTS_FILENAME));
|
||||
finalProcessor->SetAddrInterpolation(m_genInfo.GetIntermediateFileName(ADDR_INTERPOL_FILENAME));
|
||||
if (addAds)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"symbols" : [
|
||||
{
|
||||
"filename" : "speedcamera.slash.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--Generator: Apple Native CoreSVG 341-->
|
||||
<!DOCTYPE svg
|
||||
PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 3300 2200">
|
||||
<!--glyph: "", point size: 100.0, font version: "21.0d6e2", template writer version: "138.0.0"-->
|
||||
<style>.defaults {-sfsymbols-variable-value-mode:color;-sfsymbols-draw-reverses-motion-groups:true}
|
||||
|
||||
.monochrome-0 {-sfsymbols-motion-group:1;-sfsymbols-layer-tags:2e8828206d0ca565 68627a11f315162f}
|
||||
.monochrome-1 {opacity:0.0;-sfsymbols-clear-behind:true;-sfsymbols-motion-group:0;-sfsymbols-layer-tags:2e8828206d0ca565 _slash}
|
||||
.monochrome-2 {-sfsymbols-motion-group:0;-sfsymbols-layer-tags:2e8828206d0ca565 _slash}
|
||||
|
||||
.multicolor-0:tintColor {-sfsymbols-motion-group:1;-sfsymbols-layer-tags:2e8828206d0ca565 68627a11f315162f}
|
||||
.multicolor-1:tintColor {opacity:0.0;-sfsymbols-clear-behind:true;-sfsymbols-motion-group:0;-sfsymbols-layer-tags:2e8828206d0ca565 _slash}
|
||||
.multicolor-2:tintColor {-sfsymbols-motion-group:0;-sfsymbols-layer-tags:2e8828206d0ca565 _slash}
|
||||
|
||||
.hierarchical-0:secondary {-sfsymbols-motion-group:1;-sfsymbols-layer-tags:2e8828206d0ca565 68627a11f315162f}
|
||||
.hierarchical-1:primary {opacity:0.0;-sfsymbols-clear-behind:true;-sfsymbols-motion-group:0;-sfsymbols-layer-tags:2e8828206d0ca565 _slash}
|
||||
.hierarchical-2:primary {-sfsymbols-motion-group:0;-sfsymbols-layer-tags:2e8828206d0ca565 _slash}
|
||||
|
||||
.SFSymbolsPreviewWireframe {fill:none;opacity:1.0;stroke:black;stroke-width:0.5}
|
||||
</style>
|
||||
<g id="Notes">
|
||||
<rect height="2200" id="artboard" style="fill:white;opacity:1" width="3300" x="0" y="0"/>
|
||||
<line style="fill:none;stroke:black;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="292" y2="292"/>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 263 322)">Weight/Scale Variations</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 559.711 322)">Ultralight</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 856.422 322)">Thin</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 1153.13 322)">Light</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 1449.84 322)">Regular</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 1746.56 322)">Medium</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2043.27 322)">Semibold</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2339.98 322)">Bold</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2636.69 322)">Heavy</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2933.4 322)">Black</text>
|
||||
<line style="fill:none;stroke:black;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1903" y2="1903"/>
|
||||
<g transform="matrix(0.2 0 0 0.2 263 1933)">
|
||||
<path d="m46.2402 4.15039c21.7773 0 39.4531-17.627 39.4531-39.4043s-17.6758-39.4043-39.4531-39.4043c-21.7285 0-39.4043 17.627-39.4043 39.4043s17.6758 39.4043 39.4043 39.4043Zm0-7.42188c-17.6758 0-31.9336-14.3066-31.9336-31.9824s14.2578-31.9824 31.9336-31.9824 31.9824 14.3066 31.9824 31.9824-14.3066 31.9824-31.9824 31.9824Zm3.61328-17.7734v-28.4668c0-2.24609-1.46484-3.75977-3.71094-3.75977-2.14844 0-3.61328 1.51367-3.61328 3.75977v28.4668c0 2.19727 1.46484 3.71094 3.61328 3.71094 2.24609 0 3.71094-1.51367 3.71094-3.71094Zm-17.8223-10.5957h28.418c2.19727 0 3.71094-1.46484 3.71094-3.61328 0-2.19727-1.51367-3.71094-3.71094-3.71094h-28.418c-2.24609 0-3.75977 1.51367-3.75977 3.71094 0 2.14844 1.51367 3.61328 3.75977 3.61328Z"/>
|
||||
</g>
|
||||
<g transform="matrix(0.2 0 0 0.2 281.506 1933)">
|
||||
<path d="m58.5449 14.5508c27.4902 0 49.8047-22.3145 49.8047-49.8047s-22.3145-49.8047-49.8047-49.8047-49.8047 22.3145-49.8047 49.8047 22.3145 49.8047 49.8047 49.8047Zm0-8.30078c-22.9492 0-41.5039-18.5547-41.5039-41.5039s18.5547-41.5039 41.5039-41.5039 41.5039 18.5547 41.5039 41.5039-18.5547 41.5039-41.5039 41.5039Zm4.05273-23.0957v-36.9141c0-2.49023-1.70898-4.19922-4.15039-4.19922-2.39258 0-4.05273 1.70898-4.05273 4.19922v36.9141c0 2.44141 1.66016 4.15039 4.05273 4.15039 2.44141 0 4.15039-1.66016 4.15039-4.15039Zm-22.5586-14.4043h36.9629c2.44141 0 4.15039-1.61133 4.15039-4.00391 0-2.44141-1.70898-4.15039-4.15039-4.15039h-36.9629c-2.49023 0-4.15039 1.70898-4.15039 4.15039 0 2.39258 1.66016 4.00391 4.15039 4.00391Z"/>
|
||||
</g>
|
||||
<g transform="matrix(0.2 0 0 0.2 304.924 1933)">
|
||||
<path d="m74.8535 28.3203c35.1074 0 63.623-28.4668 63.623-63.5742s-28.5156-63.623-63.623-63.623-63.5742 28.5156-63.5742 63.623 28.4668 63.5742 63.5742 63.5742Zm0-9.08203c-30.127 0-54.4922-24.3652-54.4922-54.4922s24.3652-54.4922 54.4922-54.4922 54.4922 24.3652 54.4922 54.4922-24.3652 54.4922-54.4922 54.4922Zm4.44336-30.3223v-48.4863c0-2.73438-1.85547-4.63867-4.54102-4.63867-2.58789 0-4.44336 1.9043-4.44336 4.63867v48.4863c0 2.68555 1.85547 4.58984 4.44336 4.58984 2.68555 0 4.54102-1.85547 4.54102-4.58984Zm-28.7109-19.7754h48.4863c2.68555 0 4.58984-1.80664 4.58984-4.39453 0-2.73438-1.85547-4.58984-4.58984-4.58984h-48.4863c-2.73438 0-4.58984 1.85547-4.58984 4.58984 0 2.58789 1.85547 4.39453 4.58984 4.39453Z"/>
|
||||
</g>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 263 1953)">Design Variations</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1971)">Symbols are supported in up to nine weights and three scales.</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1989)">For optimal layout with text and other symbols, vertically align</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 2007)">symbols with the adjacent text.</text>
|
||||
<line style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="776" x2="776" y1="1919" y2="1933"/>
|
||||
<g transform="matrix(0.2 0 0 0.2 776 1933)">
|
||||
<path d="m16.5527 0.78125c2.58789 0 3.85742-0.976562 4.78516-3.71094l20.5566-57.5195h0.244141l20.6055 57.5195c0.927734 2.73438 2.19727 3.71094 4.73633 3.71094 2.58789 0 4.24805-1.5625 4.24805-4.00391 0-0.830078-0.146484-1.61133-0.537109-2.63672l-22.9004-60.9863c-1.12305-2.97852-3.125-4.49219-6.25-4.49219-3.02734 0-5.07812 1.46484-6.15234 4.44336l-22.9004 61.084c-0.390625 1.02539-0.537109 1.80664-0.537109 2.63672 0 2.44141 1.5625 3.95508 4.10156 3.95508Zm10.2051-20.9473h30.6641c2.00195 0 3.66211-1.66016 3.66211-3.66211 0-2.05078-1.66016-3.66211-3.66211-3.66211h-30.6641c-2.00195 0-3.66211 1.61133-3.66211 3.66211 0 2.00195 1.66016 3.66211 3.66211 3.66211Z"/>
|
||||
</g>
|
||||
<line style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="792.836" x2="792.836" y1="1919" y2="1933"/>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 776 1953)">Margins</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 1971)">Leading and trailing margins on the left and right side of each symbol</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 1989)">can be adjusted by modifying the x-location of the margin guidelines.</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 2007)">Modifications are automatically applied proportionally to all</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 2025)">scales and weights.</text>
|
||||
<g transform="matrix(0.2 0 0 0.2 1289 1933)">
|
||||
<path d="m14.209 13.1348 7.86133 7.86133c4.29688 4.39453 9.32617 4.10156 13.8672-1.02539l60.6934-68.2129-4.88281-4.88281-60.2539 67.6758c-1.80664 1.95312-3.4668 2.44141-5.81055 0.0976562l-5.17578-5.12695c-2.29492-2.29492-1.80664-3.95508 0.195312-5.81055l67.4805-62.1582-4.88281-4.83398-68.0664 62.5977c-4.98047 4.58984-5.32227 9.47266-1.02539 13.8184Zm44.873-97.4609c-2.05078 2.00195-2.24609 4.88281-1.07422 6.78711 1.12305 1.80664 3.4668 3.02734 6.5918 2.24609 5.85938-1.66016 12.5977-2.39258 18.8965 0.927734l-2.68555 7.12891c-1.61133 4.00391-0.732422 6.88477 1.70898 9.42383l10.2539 10.3027c2.34375 2.39258 4.54102 2.44141 7.08008 1.95312l4.44336-0.732422 2.58789 2.53906-0.195312 2.24609c-0.0976562 2.29492 0.537109 4.29688 2.7832 6.49414l3.36914 3.32031c2.29492 2.29492 5.51758 2.49023 7.8125 0.195312l12.9883-13.0371c2.29492-2.34375 2.14844-5.37109-0.195312-7.66602l-3.41797-3.41797c-2.19727-2.19727-4.05273-3.02734-6.34766-2.88086l-2.34375 0.244141-2.44141-2.44141 1.02539-4.6875c0.634766-2.73438-0.244141-4.98047-2.88086-7.61719l-11.2793-11.1816c-12.9395-12.8418-35.5957-11.0352-46.6797-0.146484Zm7.08008 2.05078c8.78906-6.39648 25.9766-5.66406 33.6914 1.95312l12.3047 12.207c1.02539 1.02539 1.2207 1.80664 0.927734 3.32031l-1.46484 6.64062 6.73828 6.68945 4.39453-0.244141c1.12305-0.0488281 1.51367 0.0488281 2.34375 0.878906l2.53906 2.49023-10.8398 10.8398-2.49023-2.49023c-0.830078-0.878906-0.976562-1.2207-0.927734-2.39258l0.292969-4.3457-6.68945-6.73828-6.83594 1.17188c-1.41602 0.292969-2.05078 0.195312-3.17383-0.878906l-8.93555-8.88672c-1.07422-1.02539-1.17188-1.70898-0.488281-3.36914l4.58984-11.4746c-6.10352-6.34766-17.041-7.51953-25.5859-4.58984-0.683594 0.244141-0.927734-0.390625-0.390625-0.78125Z"/>
|
||||
</g>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 1289 1953)">Exporting</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 1289 1971)">Symbols should be outlined when exporting to ensure the</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 1289 1989)">design is preserved when submitting to Xcode.</text>
|
||||
<text id="template-version" style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1933)">Template v.7.0</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1951)">Requires Xcode 17 or greater</text>
|
||||
<text id="descriptive-name" style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1969)">Generated from </text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1987)">Typeset at 100.0 points</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 726)">Small</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1156)">Medium</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1586)">Large</text>
|
||||
</g>
|
||||
<g id="Guides">
|
||||
<g id="H-reference" style="fill:#27AAE1;stroke:none;" transform="matrix(1 0 0 1 339 696)">
|
||||
<path d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"/>
|
||||
</g>
|
||||
<line id="Baseline-S" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="696" y2="696"/>
|
||||
<line id="Capline-S" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="625.541" y2="625.541"/>
|
||||
<g id="H-reference" style="fill:#27AAE1;stroke:none;" transform="matrix(1 0 0 1 339 1126)">
|
||||
<path d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"/>
|
||||
</g>
|
||||
<line id="Baseline-M" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1126" y2="1126"/>
|
||||
<line id="Capline-M" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1055.54" y2="1055.54"/>
|
||||
<g id="H-reference" style="fill:#27AAE1;stroke:none;" transform="matrix(1 0 0 1 339 1556)">
|
||||
<path d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"/>
|
||||
</g>
|
||||
<line id="Baseline-L" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1556" y2="1556"/>
|
||||
<line id="Capline-L" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1485.54" y2="1485.54"/>
|
||||
<line id="right-margin-Black-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="2991.9" x2="2991.9" y1="600.785" y2="720.121"/>
|
||||
<line id="left-margin-Black-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="2874.9" x2="2874.9" y1="600.785" y2="720.121"/>
|
||||
<line id="right-margin-Regular-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="1507.07" x2="1507.07" y1="600.785" y2="720.121"/>
|
||||
<line id="left-margin-Regular-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="1392.62" x2="1392.62" y1="600.785" y2="720.121"/>
|
||||
<line id="right-margin-Ultralight-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="614.281" x2="614.281" y1="600.785" y2="720.121"/>
|
||||
<line id="left-margin-Ultralight-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="505.141" x2="505.141" y1="600.785" y2="720.121"/>
|
||||
</g>
|
||||
<g id="Symbols">
|
||||
<g id="Black-S" transform="matrix(1 0 0 1 2874.9 696)">
|
||||
<path class="monochrome-0 multicolor-0:tintColor hierarchical-0:secondary SFSymbolsPreviewWireframe" d="M71.85-62C79.1678-62 85.1-56.0901 85.1-48.8L85.1-22.4C85.1-15.1098 79.1678-9.20001 71.85-9.20001L65.225-9.20001L65.225 4L51.975 4L51.975-9.20001L45.35-9.20001C38.0322-9.20001 32.1-15.1098 32.1-22.4L32.1-48.8C32.1-56.0901 38.0322-62 45.35-62L71.85-62ZM41.6-22C39.667-22 38.1-20.433 38.1-18.5C38.1-16.567 39.667-15 41.6-15C43.533-15 45.1-16.567 45.1-18.5C45.1-20.433 43.533-22 41.6-22ZM58.6004-49C51.144-49 45.1-42.9559 45.1-35.5C45.1-28.3424 50.6701-22.4858 57.712-22.0287L58.6004-22C66.056-22 72.1-28.0442 72.1-35.5C72.1-42.9559 66.056-49 58.6004-49ZM71.4333-76C86.1609-76 98.1-63.7624 98.1-48.6667L98.1-35L91.4334-35L91.4334-48.6667C91.4334-59.9885 82.479-69.1667 71.4333-69.1667L44.7667-69.1667C33.721-69.1667 24.7667-59.9885 24.7667-48.6667L24.7667-35L18.1-35L18.1-48.6667C18.1-63.7624 30.0391-76 44.7667-76L71.4333-76Z"/>
|
||||
<path class="monochrome-1 multicolor-1:tintColor hierarchical-1:primary SFSymbolsPreviewWireframe" d="M102.472-4.48836C106.123-0.837264 106.123 5.09118 102.472 8.74227C98.8207 12.3934 92.8922 12.3934 89.2411 8.74227L14.5282-65.9706C10.8771-69.6217 10.8771-75.5502 14.5282-79.2013C18.1793-82.8524 24.1078-82.8524 27.7589-79.2013Z" data-clipstroke-keyframes="0 0 0 0.49990463 0.6089134 0 1 0 0.10891342"/>
|
||||
<path class="monochrome-2 multicolor-2:tintColor hierarchical-2:primary SFSymbolsPreviewWireframe" d="M98.3217-0.338247C99.6822 1.02234 99.6822 3.23158 98.3217 4.59217C96.9611 5.95275 94.7518 5.95275 93.3912 4.59217L18.6783-70.1207C17.3178-71.4813 17.3178-73.6906 18.6783-75.0511C20.0389-76.4117 22.2482-76.4117 23.6088-75.0511Z" data-clipstroke-keyframes="0 0 0 0.49988937 0.54707384 0 1 0 0.04707384"/>
|
||||
</g>
|
||||
<g id="Regular-S" transform="matrix(1 0 0 1 1392.62 696)">
|
||||
<path class="monochrome-0 multicolor-0:tintColor hierarchical-0:secondary SFSymbolsPreviewWireframe" d="M69.9575-61.1549C76.9038-61.1549 82.5349-55.4998 82.5349-48.524L82.5349-23.262C82.5349-16.2861 76.9038-10.631 69.9575-10.631L63.6687-10.631L63.6687 2L51.0913 2L51.0913-10.631L44.8025-10.631C37.8562-10.631 32.2251-16.2861 32.2251-23.262L32.2251-48.524C32.2251-55.4998 37.8562-61.1549 44.8025-61.1549L69.9575-61.1549ZM41.8589-22.6197C40.0853-22.6197 38.6476-21.182 38.6476-19.4085C38.6476-17.6349 40.0853-16.1972 41.8589-16.1972C43.6324-16.1972 45.0701-17.6349 45.0701-19.4085C45.0701-21.182 43.6324-22.6197 41.8589-22.6197ZM57.9156-48.3099C50.8209-48.3099 45.0701-42.5589 45.0701-35.4648C45.0701-28.6544 50.3701-23.0819 57.0703-22.647L57.9156-22.6197C65.0095-22.6197 70.7603-28.3707 70.7603-35.4648C70.7603-42.5589 65.0095-48.3099 57.9156-48.3099ZM70.0467-74C84.0379-74 95.38-62.4981 95.38-48.3099L95.38-35.4648L89.0467-35.4648L89.0467-48.3099C89.0467-58.9511 80.5401-67.5775 70.0467-67.5775L44.7133-67.5775C34.2199-67.5775 25.7133-58.9511 25.7133-48.3099L25.7133-35.4648L19.38-35.4648L19.38-48.3099C19.38-62.4981 30.7221-74 44.7133-74L70.0467-74Z"/>
|
||||
<path class="monochrome-1 multicolor-1:tintColor hierarchical-1:primary SFSymbolsPreviewWireframe" d="M98.2783-6.06714C101.56-2.78573 101.56 2.54245 98.2783 5.82386C94.9969 9.10527 89.6688 9.10527 86.3874 5.82386L16.1717-64.3918C12.8902-67.6733 12.8902-73.0014 16.1717-76.2828C19.4531-79.5643 24.7812-79.5643 28.0626-76.2828Z" data-clipstroke-keyframes="0 0 0 0.50010824 0.6049547 0 1 0 0.10495472"/>
|
||||
<path class="monochrome-2 multicolor-2:tintColor hierarchical-2:primary SFSymbolsPreviewWireframe" d="M94.8119-2.60066C96.1801-1.23245 96.1801 0.98917 94.8119 2.35738C93.4437 3.72558 91.222 3.72558 89.8538 2.35738L19.6381-67.8583C18.2699-69.2265 18.2699-71.4482 19.6381-72.8164C21.0063-74.1846 23.228-74.1846 24.5962-72.8164Z" data-clipstroke-keyframes="0 0 0 0.50020504 0.5497174 0 1 0 0.049717426"/>
|
||||
</g>
|
||||
<g id="Ultralight-S" transform="matrix(1 0 0 1 505.141 696)">
|
||||
<path class="monochrome-0 multicolor-0:tintColor hierarchical-0:secondary SFSymbolsPreviewWireframe" d="M66.4435-59.169C72.8414-59.169 78.028-53.9604 78.028-47.5352L78.028-24.2676C78.028-17.8424 72.8414-12.6338 66.4435-12.6338L60.6513-12.6338L60.6513-1L49.0668-1L49.0668-12.6338L43.2745-12.6338C36.8765-12.6338 31.69-17.8424 31.69-24.2676L31.69-47.5352C31.69-53.9604 36.8765-59.169 43.2745-59.169L66.4435-59.169ZM40.5632-23.6761C38.9297-23.6761 37.6055-22.3518 37.6055-20.7183C37.6055-19.0848 38.9297-17.7606 40.5632-17.7606C42.1967-17.7606 43.521-19.0848 43.521-20.7183C43.521-22.3518 42.1967-23.6761 40.5632-23.6761ZM55.3523-47.338C48.8177-47.338 43.521-42.0411 43.521-35.5071C43.521-29.2343 48.4025-24.1018 54.5738-23.7012L55.3523-23.6761C61.8862-23.6761 67.1829-28.973 67.1829-35.5071C67.1829-42.0411 61.8862-47.338 55.3523-47.338ZM66.5257-71C79.4123-71 89.859-60.4062 89.859-47.338L89.859-35.507L84.0257-35.507L84.0257-47.338C84.0257-57.1391 76.1907-65.0845 66.5257-65.0845L43.1923-65.0845C33.5273-65.0845 25.6923-57.1391 25.6923-47.338L25.6923-35.507L19.859-35.507L19.859-47.338C19.859-60.4062 30.3057-71 43.1923-71L66.5257-71Z"/>
|
||||
<path class="monochrome-1 multicolor-1:tintColor hierarchical-1:primary SFSymbolsPreviewWireframe" d="M92.2562-3.06064C93.7788-1.53808 93.7788 0.934172 92.2562 2.45673C90.7337 3.97929 88.2614 3.97929 86.7389 2.45673L16.8838-67.3983C15.3612-68.9209 15.3612-71.3932 16.8838-72.9157C18.4063-74.4383 20.8786-74.4383 22.4011-72.9157Z" data-clipstroke-keyframes="0 0 0 0.5001135 0.5550747 0 1 0 0.05507469"/>
|
||||
<path class="monochrome-2 multicolor-2:tintColor hierarchical-2:primary SFSymbolsPreviewWireframe" d="M90.2053-1.00975C90.596-0.619106 90.596 0.0152006 90.2053 0.405845C89.8147 0.796489 89.1804 0.796489 88.7897 0.405845L18.9347-69.4492C18.544-69.8399 18.544-70.4742 18.9347-70.8648C19.3253-71.2555 19.9596-71.2555 20.3503-70.8648Z" data-clipstroke-keyframes="0 0 0 0.50012684 0.51529884 0 1 0 0.015298843"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 20 KiB |
@@ -1522,6 +1522,7 @@
|
||||
"type.tourism.information.board" = "Information Board";
|
||||
"type.tourism.information.guidepost" = "Guidepost";
|
||||
"type.tourism.information.map" = "Tourist Map";
|
||||
"type.tourism.information.tactile_map" = "Tactile Map";
|
||||
"type.tourism.information.office" = "Tourist Office";
|
||||
"type.tourism.information.visitor_centre" = "Visitor Centre";
|
||||
"type.amenity.ranger_station" = "Ranger Station";
|
||||
|
||||
@@ -706,6 +706,7 @@ void Framework::FillInfoFromFeatureType(FeatureType & ft, place_page::Info & inf
|
||||
info.SetFromFeatureType(ft);
|
||||
|
||||
FillDescription(ft, info);
|
||||
CheckPanoramaxImagery(info);
|
||||
|
||||
auto const mwmInfo = ft.GetID().m_mwmId.GetInfo();
|
||||
bool const isMapVersionEditable = CanEditMapForPosition(info.GetMercator());
|
||||
@@ -3263,6 +3264,43 @@ void Framework::FillDescription(FeatureType & ft, place_page::Info & info) const
|
||||
}
|
||||
}
|
||||
|
||||
void Framework::CheckPanoramaxImagery(place_page::Info & info) const
|
||||
{
|
||||
// Query features within 50m radius
|
||||
auto constexpr radiusM = 50.0;
|
||||
auto const center = info.GetMercator();
|
||||
auto const rect = mercator::RectByCenterXYAndSizeInMeters(center, radiusM);
|
||||
|
||||
auto const panoramaxType = classif().GetTypeByPath({"panoramax", "image"});
|
||||
|
||||
bool hasPanoramax = false;
|
||||
std::string panoramaxImageId;
|
||||
std::string panoramaxUrl;
|
||||
|
||||
m_featuresFetcher.GetDataSource().ForEachInRect([&](FeatureType & ft)
|
||||
{
|
||||
if (ft.GetTypes().Has(panoramaxType))
|
||||
{
|
||||
auto const imageId = ft.GetMetadata(feature::Metadata::FMD_PANORAMAX);
|
||||
if (!imageId.empty())
|
||||
{
|
||||
hasPanoramax = true;
|
||||
panoramaxImageId = std::string(imageId);
|
||||
panoramaxUrl = "https://panoramax.openstreetmap.fr/#focus=pic:" + panoramaxImageId;
|
||||
return base::ControlFlow::Break; // Found one, stop searching
|
||||
}
|
||||
}
|
||||
return base::ControlFlow::Continue;
|
||||
}, rect, df::GetDrawTileScale(rect));
|
||||
|
||||
if (hasPanoramax)
|
||||
{
|
||||
info.m_hasPanoramax = true;
|
||||
info.m_panoramaxImageId = std::move(panoramaxImageId);
|
||||
info.m_panoramaxUrl = std::move(panoramaxUrl);
|
||||
}
|
||||
}
|
||||
|
||||
void Framework::OnPowerFacilityChanged(power_management::Facility const facility, bool enabled)
|
||||
{
|
||||
if (facility == power_management::Facility::PerspectiveView || facility == power_management::Facility::Buildings3d)
|
||||
|
||||
@@ -640,6 +640,7 @@ private:
|
||||
void FillTrackInfo(Track const & track, m2::PointD const & trackPoint, place_page::Info & info) const;
|
||||
void SetPlacePageLocation(place_page::Info & info);
|
||||
void FillDescription(FeatureType & ft, place_page::Info & info) const;
|
||||
void CheckPanoramaxImagery(place_page::Info & info) const;
|
||||
|
||||
public:
|
||||
search::ReverseGeocoder::Address GetAddressAtPoint(m2::PointD const & pt) const;
|
||||
|
||||
@@ -114,6 +114,9 @@ public:
|
||||
bool HasApiUrl() const { return !m_apiUrl.empty(); }
|
||||
/// TODO: Support all possible Internet types in UI. @See MapObject::GetInternet().
|
||||
bool HasWifi() const { return GetInternet() == feature::Internet::Wlan; }
|
||||
/// @returns true if Panoramax imagery is available within 50m.
|
||||
bool HasPanoramax() const { return m_hasPanoramax; }
|
||||
std::string const & GetPanoramaxUrl() const { return m_panoramaxUrl; }
|
||||
/// Should be used by UI code to generate cool name for new bookmarks.
|
||||
// TODO: Tune new bookmark name. May be add address or some other data.
|
||||
kml::LocalizableString FormatNewBookmarkName() const;
|
||||
@@ -258,6 +261,11 @@ private:
|
||||
/// Formatted feature address for inner using.
|
||||
std::string m_address;
|
||||
|
||||
/// Panoramax
|
||||
bool m_hasPanoramax = false;
|
||||
std::string m_panoramaxImageId;
|
||||
std::string m_panoramaxUrl;
|
||||
|
||||
/// Routing
|
||||
RouteMarkType m_routeMarkType;
|
||||
size_t m_intermediateIndex = 0;
|
||||
|
||||
@@ -127,6 +127,8 @@ PreferencesDialog::PreferencesDialog(QWidget * parent, Framework & framework)
|
||||
|
||||
QStringList languagesList = QStringList();
|
||||
std::vector<size_t> sortedIndices;
|
||||
languagesList << QString::fromStdString("Local Language");
|
||||
sortedIndices.push_back(0);
|
||||
for (auto const & pair : languageNameIndexPairs)
|
||||
{
|
||||
languagesList << QString::fromStdString(pair.first);
|
||||
@@ -143,6 +145,11 @@ PreferencesDialog::PreferencesDialog(QWidget * parent, Framework & framework)
|
||||
QString::fromStdString(std::string(StringUtf8Multilang::GetLangNameByCode(languageIndex))));
|
||||
connect(mapLanguageComboBox, &QComboBox::activated, [&framework, &supportedLanguages, sortedIndices](int index)
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
framework.SetMapLanguageCode("default");
|
||||
return;
|
||||
}
|
||||
auto const & mapLanguageCode = std::string(supportedLanguages[sortedIndices[index]].m_code);
|
||||
framework.SetMapLanguageCode(mapLanguageCode);
|
||||
});
|
||||
|
||||
@@ -351,6 +351,10 @@ class PathProvider:
|
||||
def addresses_path() -> AnyStr:
|
||||
return settings.ADDRESSES_PATH
|
||||
|
||||
@staticmethod
|
||||
def panoramax_path() -> AnyStr:
|
||||
return settings.PANORAMAX_PATH
|
||||
|
||||
@staticmethod
|
||||
def borders_path() -> AnyStr:
|
||||
return os.path.join(settings.USER_RESOURCE_PATH, "borders")
|
||||
|
||||
@@ -60,6 +60,7 @@ class GenTool:
|
||||
"intermediate_data_path": str,
|
||||
"isolines_path": str,
|
||||
"addresses_path": str,
|
||||
"panoramax_path": str,
|
||||
"nodes_list_path": str,
|
||||
"node_storage": str,
|
||||
"osm_file_name": str,
|
||||
|
||||
@@ -121,6 +121,7 @@ US_POSTCODES_URL = ""
|
||||
SRTM_PATH = ""
|
||||
ISOLINES_PATH = ""
|
||||
ADDRESSES_PATH = ""
|
||||
PANORAMAX_PATH = ""
|
||||
|
||||
# Stats section:
|
||||
STATS_TYPES_CONFIG = os.path.join(ETC_DIR, "stats_types_config.txt")
|
||||
@@ -278,6 +279,7 @@ def init(default_settings_path: AnyStr):
|
||||
global SRTM_PATH
|
||||
global ISOLINES_PATH
|
||||
global ADDRESSES_PATH
|
||||
global PANORAMAX_PATH
|
||||
|
||||
PLANET_URL = cfg.get_opt_path("External", "PLANET_URL", PLANET_URL)
|
||||
PLANET_MD5_URL = cfg.get_opt_path("External", "PLANET_MD5_URL", md5_ext(PLANET_URL))
|
||||
@@ -306,6 +308,7 @@ def init(default_settings_path: AnyStr):
|
||||
SRTM_PATH = cfg.get_opt_path("External", "SRTM_PATH", SRTM_PATH)
|
||||
ISOLINES_PATH = cfg.get_opt_path("External", "ISOLINES_PATH", ISOLINES_PATH)
|
||||
ADDRESSES_PATH = cfg.get_opt_path("External", "ADDRESSES_PATH", ADDRESSES_PATH)
|
||||
PANORAMAX_PATH = cfg.get_opt_path("External", "PANORAMAX_PATH", PANORAMAX_PATH)
|
||||
|
||||
# Stats section:
|
||||
global STATS_TYPES_CONFIG
|
||||
|
||||
@@ -134,6 +134,8 @@ class StageFeatures(Stage):
|
||||
if is_accepted(env, StageIsolinesInfo):
|
||||
extra.update({"isolines_path": PathProvider.isolines_path()})
|
||||
extra.update({"addresses_path": PathProvider.addresses_path()})
|
||||
if PathProvider.panoramax_path():
|
||||
extra.update({"panoramax_path": PathProvider.panoramax_path()})
|
||||
|
||||
steps.step_features(env, **extra)
|
||||
if os.path.exists(env.paths.packed_polygons_path):
|
||||
|
||||
403
tools/python/maps_generator/panoramax_preprocessor.py
Normal file
403
tools/python/maps_generator/panoramax_preprocessor.py
Normal file
@@ -0,0 +1,403 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Panoramax Preprocessor
|
||||
|
||||
Converts the global Panoramax geoparquet file into per-country binary files
|
||||
for use in the map generator.
|
||||
|
||||
The script streams the large geoparquet file (20GB+) using DuckDB to avoid
|
||||
loading everything into memory, performs a spatial join with country polygons,
|
||||
and writes compact binary files for each country.
|
||||
|
||||
Binary Format:
|
||||
Header:
|
||||
uint32 version (=1)
|
||||
uint64 point_count
|
||||
Data (repeated point_count times):
|
||||
double lat (8 bytes)
|
||||
double lon (8 bytes)
|
||||
string image_id (length-prefixed: uint32 length + bytes)
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Tuple
|
||||
from collections import defaultdict
|
||||
|
||||
try:
|
||||
import duckdb
|
||||
except ImportError:
|
||||
print("Error: duckdb is required. Install with: pip install duckdb", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
from shapely.geometry import Point, Polygon, MultiPolygon
|
||||
from shapely.strtree import STRtree
|
||||
except ImportError:
|
||||
print("Error: shapely is required. Install with: pip install shapely", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def parse_poly_file(poly_path: Path) -> MultiPolygon:
|
||||
"""
|
||||
Parse an Osmosis .poly file and return a Shapely MultiPolygon.
|
||||
|
||||
.poly format:
|
||||
Line 1: Region name
|
||||
Section N: (numbered 1, 2, 3...)
|
||||
lon lat (pairs of coordinates)
|
||||
...
|
||||
END
|
||||
"""
|
||||
with open(poly_path, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
polygons = []
|
||||
current_coords = []
|
||||
in_section = False
|
||||
|
||||
for line in lines[1:]: # Skip first line (region name)
|
||||
line = line.strip()
|
||||
|
||||
if not line:
|
||||
continue
|
||||
|
||||
if line.upper() == 'END':
|
||||
if current_coords:
|
||||
# Close the polygon if needed
|
||||
if current_coords[0] != current_coords[-1]:
|
||||
current_coords.append(current_coords[0])
|
||||
|
||||
# Create polygon (need at least 3 points + closing point)
|
||||
if len(current_coords) >= 4:
|
||||
try:
|
||||
poly = Polygon(current_coords)
|
||||
|
||||
# If polygon is invalid, try to fix it
|
||||
if not poly.is_valid:
|
||||
# Try buffer(0) trick to fix self-intersections
|
||||
poly = poly.buffer(0)
|
||||
|
||||
# Only accept if it's now valid and is a Polygon or MultiPolygon
|
||||
if poly.is_valid and not poly.is_empty:
|
||||
if poly.geom_type == 'Polygon':
|
||||
polygons.append(poly)
|
||||
elif poly.geom_type == 'MultiPolygon':
|
||||
# Split multipolygon into individual polygons
|
||||
polygons.extend(poly.geoms)
|
||||
else:
|
||||
logger.debug(f"Skipping invalid section in {poly_path.name}")
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"Error creating polygon in {poly_path.name}: {e}")
|
||||
|
||||
current_coords = []
|
||||
in_section = False
|
||||
continue
|
||||
|
||||
# Try to parse as section number
|
||||
try:
|
||||
int(line)
|
||||
in_section = True
|
||||
continue
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Parse coordinate pair
|
||||
if in_section:
|
||||
parts = line.split()
|
||||
if len(parts) >= 2:
|
||||
try:
|
||||
lon = float(parts[0])
|
||||
lat = float(parts[1])
|
||||
current_coords.append((lon, lat))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if not polygons:
|
||||
logger.warning(f"No valid polygons found in {poly_path.name}")
|
||||
return None
|
||||
|
||||
if len(polygons) == 1:
|
||||
return MultiPolygon([polygons[0]])
|
||||
else:
|
||||
return MultiPolygon(polygons)
|
||||
|
||||
|
||||
def load_country_polygons(borders_dir: Path) -> Dict[str, MultiPolygon]:
|
||||
"""
|
||||
Load all .poly files from the borders directory.
|
||||
|
||||
Returns a dict mapping region name (without .poly extension) to MultiPolygon.
|
||||
"""
|
||||
logger.info(f"Loading .poly files from {borders_dir}")
|
||||
|
||||
poly_files = list(borders_dir.glob("*.poly"))
|
||||
logger.info(f"Found {len(poly_files)} .poly files")
|
||||
|
||||
polygons = {}
|
||||
|
||||
for poly_file in poly_files:
|
||||
region_name = poly_file.stem # Filename without .poly extension
|
||||
|
||||
try:
|
||||
multi_polygon = parse_poly_file(poly_file)
|
||||
if multi_polygon:
|
||||
polygons[region_name] = multi_polygon
|
||||
except Exception as e:
|
||||
logger.error(f"Error parsing {poly_file.name}: {e}")
|
||||
continue
|
||||
|
||||
logger.info(f"Successfully loaded {len(polygons)} region polygons")
|
||||
return polygons
|
||||
|
||||
|
||||
class RegionFinder:
|
||||
"""
|
||||
Efficient spatial index for finding which region a point belongs to.
|
||||
Uses Shapely's STRtree for fast spatial queries.
|
||||
"""
|
||||
def __init__(self, regions: Dict[str, MultiPolygon]):
|
||||
logger.info("Building spatial index for region lookup...")
|
||||
|
||||
self.regions = regions
|
||||
self.region_names = []
|
||||
self.geometries = []
|
||||
|
||||
for region_name, multi_polygon in regions.items():
|
||||
self.region_names.append(region_name)
|
||||
self.geometries.append(multi_polygon)
|
||||
|
||||
# Build R-tree spatial index for fast lookups
|
||||
self.tree = STRtree(self.geometries)
|
||||
|
||||
logger.info(f"Spatial index built with {len(self.geometries)} regions")
|
||||
|
||||
def find_region(self, lat: float, lon: float) -> str:
|
||||
"""
|
||||
Find which region a coordinate belongs to.
|
||||
|
||||
Returns region name or None if not found.
|
||||
"""
|
||||
point = Point(lon, lat) # Note: Shapely uses (x, y) = (lon, lat)
|
||||
|
||||
# Query the spatial index for candidate polygons
|
||||
candidates = self.tree.query(point)
|
||||
|
||||
# Check each candidate to see if point is actually inside
|
||||
for idx in candidates:
|
||||
if self.geometries[idx].contains(point):
|
||||
return self.region_names[idx]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def write_binary_file(output_path: Path, points: List[Tuple[float, float, str]]):
|
||||
"""
|
||||
Write panoramax points to binary file.
|
||||
|
||||
Format:
|
||||
Header:
|
||||
uint32 version = 1
|
||||
uint64 point_count
|
||||
Data:
|
||||
For each point:
|
||||
double lat
|
||||
double lon
|
||||
uint32 image_id_length
|
||||
bytes image_id
|
||||
"""
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(output_path, 'wb') as f:
|
||||
# Write header
|
||||
version = 1
|
||||
point_count = len(points)
|
||||
f.write(struct.pack('<I', version)) # uint32 version
|
||||
f.write(struct.pack('<Q', point_count)) # uint64 point_count
|
||||
|
||||
# Write points
|
||||
for lat, lon, image_id in points:
|
||||
f.write(struct.pack('<d', lat)) # double lat
|
||||
f.write(struct.pack('<d', lon)) # double lon
|
||||
|
||||
# Write image_id as length-prefixed string
|
||||
image_id_bytes = image_id.encode('utf-8')
|
||||
f.write(struct.pack('<I', len(image_id_bytes))) # uint32 length
|
||||
f.write(image_id_bytes) # bytes
|
||||
|
||||
logger.info(f"Wrote {point_count} points to {output_path}")
|
||||
|
||||
|
||||
def process_parquet_streaming(parquet_url: str, output_dir: Path, borders_dir: Path, batch_size: int = 100000):
|
||||
"""
|
||||
Stream the Panoramax parquet file and write per-country binary files.
|
||||
|
||||
Uses DuckDB to stream the large parquet file without loading it entirely into memory.
|
||||
Uses .poly files from borders_dir to categorize points into regions.
|
||||
"""
|
||||
# Load region polygons and build spatial index
|
||||
regions = load_country_polygons(borders_dir)
|
||||
if not regions:
|
||||
logger.error("No regions loaded - cannot process panoramax data")
|
||||
return
|
||||
|
||||
region_finder = RegionFinder(regions)
|
||||
|
||||
conn = duckdb.connect(database=':memory:')
|
||||
|
||||
# Enable httpfs extension for remote file access
|
||||
try:
|
||||
conn.execute("INSTALL httpfs;")
|
||||
conn.execute("LOAD httpfs;")
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not load httpfs extension: {e}")
|
||||
|
||||
# Install spatial extension for future country boundary support
|
||||
try:
|
||||
conn.execute("INSTALL spatial;")
|
||||
conn.execute("LOAD spatial;")
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not load spatial extension: {e}")
|
||||
|
||||
logger.info(f"Reading parquet file: {parquet_url}")
|
||||
|
||||
# First, inspect the schema to understand the columns
|
||||
try:
|
||||
schema_result = conn.execute(f"DESCRIBE SELECT * FROM read_parquet('{parquet_url}') LIMIT 0").fetchall()
|
||||
logger.info(f"Parquet schema: {[col[0] for col in schema_result]}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not read schema: {e}")
|
||||
|
||||
# Dictionary to accumulate points per country
|
||||
country_points: Dict[str, List[Tuple[float, float, str]]] = defaultdict(list)
|
||||
|
||||
# Stream the parquet file in batches
|
||||
# Geoparquet stores geometry as GEOMETRY type
|
||||
# Use DuckDB spatial functions to extract lat/lon
|
||||
query = f"""
|
||||
SELECT
|
||||
ST_Y(geometry) as lat,
|
||||
ST_X(geometry) as lon,
|
||||
id as image_id
|
||||
FROM read_parquet('{parquet_url}')
|
||||
WHERE geometry IS NOT NULL
|
||||
"""
|
||||
|
||||
try:
|
||||
result = conn.execute(query)
|
||||
|
||||
batch_count = 0
|
||||
total_points = 0
|
||||
|
||||
while True:
|
||||
batch = result.fetchmany(batch_size)
|
||||
if not batch:
|
||||
break
|
||||
|
||||
batch_count += 1
|
||||
batch_size_actual = len(batch)
|
||||
total_points += batch_size_actual
|
||||
|
||||
logger.info(f"Processing batch {batch_count}: {batch_size_actual} points (total: {total_points})")
|
||||
|
||||
for row in batch:
|
||||
lat, lon, image_id = row
|
||||
|
||||
# Find which region this point belongs to
|
||||
region = region_finder.find_region(lat, lon)
|
||||
|
||||
# Only add points that fall within a defined region
|
||||
if region:
|
||||
country_points[region].append((lat, lon, str(image_id)))
|
||||
|
||||
# Periodically write to disk to avoid memory issues
|
||||
if batch_count % 10 == 0:
|
||||
for country, points in country_points.items():
|
||||
if len(points) > 100000: # Write if accumulated > 100k points
|
||||
output_file = output_dir / f"{country}.panoramax"
|
||||
# Append mode for incremental writing
|
||||
# TODO: Implement append mode or accumulate all then write once
|
||||
logger.info(f"Country {country} has {len(points)} points accumulated")
|
||||
|
||||
logger.info(f"Finished processing {total_points} total points")
|
||||
logger.info(f"Countries found: {list(country_points.keys())}")
|
||||
|
||||
# Write final output files
|
||||
for country, points in country_points.items():
|
||||
if points:
|
||||
output_file = output_dir / f"{country}.panoramax"
|
||||
write_binary_file(output_file, points)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing parquet: {e}")
|
||||
raise
|
||||
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Convert Panoramax geoparquet to per-country binary files",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog=__doc__
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--input',
|
||||
default='https://api.panoramax.xyz/data/geoparquet/panoramax.parquet',
|
||||
help='Path or URL to Panoramax geoparquet file (default: official Panoramax URL)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--output',
|
||||
type=Path,
|
||||
required=True,
|
||||
help='Output directory for per-country .panoramax files'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--borders-dir',
|
||||
type=Path,
|
||||
default=Path(__file__).parent.parent.parent.parent / 'data' / 'borders',
|
||||
help='Path to directory containing .poly border files (default: <repo>/data/borders)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--batch-size',
|
||||
type=int,
|
||||
default=100000,
|
||||
help='Number of rows to process per batch (default: 100000)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
logger.info("Panoramax Preprocessor starting")
|
||||
logger.info(f"Input: {args.input}")
|
||||
logger.info(f"Output directory: {args.output}")
|
||||
logger.info(f"Borders directory: {args.borders_dir}")
|
||||
logger.info(f"Batch size: {args.batch_size}")
|
||||
|
||||
# Verify borders directory exists
|
||||
if not args.borders_dir.exists():
|
||||
logger.error(f"Borders directory not found: {args.borders_dir}")
|
||||
sys.exit(1)
|
||||
|
||||
# Create output directory
|
||||
args.output.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Process the parquet file
|
||||
process_parquet_streaming(args.input, args.output, args.borders_dir, args.batch_size)
|
||||
|
||||
logger.info("Panoramax preprocessing complete!")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -80,6 +80,7 @@ SUBWAY_URL: file:///home/planet/subway/subways.transit.json
|
||||
SRTM_PATH: /home/planet/SRTM-patched-europe/
|
||||
ISOLINES_PATH: /home/planet/isolines/
|
||||
ADDRESSES_PATH: /home/planet/tiger/
|
||||
PANORAMAX_PATH: /home/planet/panoramax/countries/
|
||||
|
||||
# Local path (not url!) to .csv files.
|
||||
UK_POSTCODES_URL: /home/planet/postcodes/gb-postcode-data/gb_postcodes.csv
|
||||
@@ -99,7 +100,7 @@ DIFF_VERSION_DEPTH: 2
|
||||
|
||||
[Common]
|
||||
# Default parallelism level for the most of jobs. Set to 0 for auto detection.
|
||||
THREADS_COUNT: 56
|
||||
THREADS_COUNT: 0
|
||||
|
||||
|
||||
[Stats]
|
||||
|
||||
@@ -13,6 +13,7 @@ mkdir -p /home/planet/postcodes/gb-postcode-data/
|
||||
mkdir -p /home/planet/postcodes/us-postcodes/
|
||||
mkdir -p /home/planet/SRTM-patched-europe/
|
||||
mkdir -p /home/planet/subway
|
||||
mkdir -p /home/planet/panoramax/countries/
|
||||
|
||||
echo "<$(date +%T)> Running ./configure.sh ..."
|
||||
cd ~/comaps
|
||||
|
||||
Reference in New Issue
Block a user