Merge branch 'organicmaps:master' into always_show_next_turn_option
This commit is contained in:
commit
656686cbb4
2392 changed files with 123874 additions and 166477 deletions
|
@ -163,8 +163,6 @@ IncludeCategories:
|
|||
- Regex: '^"openlr/openlr_stat/'
|
||||
Priority: 19400
|
||||
|
||||
- Regex: '^"mapshot/'
|
||||
Priority: 19500
|
||||
|
||||
- Regex: '^"search/search_quality/booking_dataset_generator/'
|
||||
Priority: 19707
|
||||
|
@ -260,9 +258,6 @@ IncludeCategories:
|
|||
- Regex: '^"editor/'
|
||||
Priority: 48310
|
||||
|
||||
- Regex: '^"software_renderer/'
|
||||
Priority: 48400
|
||||
|
||||
- Regex: '^"drape_frontend/'
|
||||
Priority: 48500
|
||||
|
||||
|
|
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
|
@ -1,3 +1,4 @@
|
|||
github: organicmaps
|
||||
liberapay: OrganicMaps
|
||||
custom: ["https://organicmaps.app/donate"]
|
||||
open_collective: organicmaps
|
||||
custom: ["https://organicmaps.app/donate/", "https://donate.organicmaps.app/"]
|
||||
|
|
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -7,6 +7,8 @@ assignees: ''
|
|||
|
||||
---
|
||||
|
||||
⚠ Have you searched for similar, already existing issues?
|
||||
|
||||
**Describe the issue**
|
||||
Please write a clear and concise description of the issue here.
|
||||
|
||||
|
@ -28,7 +30,7 @@ If applicable, add screenshots or screen recordings to help explain your problem
|
|||
|
||||
**System information:**
|
||||
- Operating system and its version: [iOS 12, Android 10, Ubuntu 22, MacOS Big Sur, etc.]
|
||||
- Organic Maps version: [you can find it by tapping the button with the green Organic Maps logo, bottom left of the main screen on Android, or the `?` button on iOS]
|
||||
- Organic Maps version: [you can find it by tapping the button with the green Organic Maps logo]
|
||||
- Device Model: [e.g. iPhone 6, Samsung S22]
|
||||
|
||||
|
||||
|
|
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -7,6 +7,8 @@ assignees: ''
|
|||
|
||||
---
|
||||
|
||||
⚠ Have you searched for similar, already existing issues?
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. For example:
|
||||
I'm always frustrated when [...]
|
||||
|
|
2
.github/workflows/android-beta.yaml
vendored
2
.github/workflows/android-beta.yaml
vendored
|
@ -76,7 +76,7 @@ jobs:
|
|||
run: |
|
||||
cmake --version
|
||||
ninja --version
|
||||
./gradlew -Pfirebase -x lint -x lintVitalGoogleBeta assembleGoogleBeta uploadCrashlyticsSymbolFileGoogleBeta uploadCrashlyticsMappingFileGoogleBeta
|
||||
./gradlew -Pfirebase assembleGoogleBeta uploadCrashlyticsSymbolFileGoogleBeta uploadCrashlyticsMappingFileGoogleBeta
|
||||
|
||||
- name: Upload beta apk to App Distribution
|
||||
shell: bash
|
||||
|
|
25
.github/workflows/android-monkey.yaml
vendored
25
.github/workflows/android-monkey.yaml
vendored
|
@ -31,8 +31,8 @@ jobs:
|
|||
outputs:
|
||||
updated: ${{ steps.check.outputs.updated }}
|
||||
|
||||
android-google-debug:
|
||||
name: Android Google Debug
|
||||
android-google-beta:
|
||||
name: Android Google Beta
|
||||
runs-on: ubuntu-latest
|
||||
needs: precondition
|
||||
if: ${{ needs.precondition.outputs.updated != '' }}
|
||||
|
@ -76,25 +76,20 @@ jobs:
|
|||
run: |
|
||||
cmake --version
|
||||
ninja --version
|
||||
./gradlew -Pfirebase -Parm64-v8a -Parmeabi-v7a -Px86_64 assembleGoogleDebug uploadCrashlyticsSymbolFileGoogleDebug
|
||||
./gradlew -Pfirebase -Parm64 -Parmeabi-v7a assembleGoogleBeta uploadCrashlyticsSymbolFileGoogleBeta uploadCrashlyticsMappingFileGoogleBeta
|
||||
|
||||
- name: Run monkey
|
||||
run: |
|
||||
gcloud auth activate-service-account --key-file android/app/firebase-test-lab.json
|
||||
gcloud config set project omapsapp
|
||||
gcloud firebase test android run --app ./android/app/build/outputs/apk/google/debug/OrganicMaps-*-google-debug.apk \
|
||||
gcloud firebase test android run --app ./android/app/build/outputs/apk/google/beta/OrganicMaps-*-google-beta.apk \
|
||||
--device model=husky,version=34 \
|
||||
--device model=cheetah,version=33 \
|
||||
--device model=tangorpro,version=33,orientation=landscape \
|
||||
--device model=bluejay,version=32 \
|
||||
--device model=a51,version=31 \
|
||||
--device model=f2q,version=30 \
|
||||
--device model=a10,version=29 \
|
||||
--device model=Pixel2.arm,version=30 \
|
||||
--device model=MediumPhone.arm,version=29 \
|
||||
--device model=MediumPhone.arm,version=28 \
|
||||
--device model=MediumPhone.arm,version=27 \
|
||||
--device model=Pixel2.arm,version=26,orientation=landscape \
|
||||
--device model=Nexus6,version=25 \
|
||||
--device model=NexusLowRes,version=24 \
|
||||
--device model=NexusLowRes,version=23,orientation=landscape \
|
||||
--device model=f2q,version=30,orientation=landscape \
|
||||
--device model=a10,version=29,orientation=landscape \
|
||||
--device model=cactus,version=27 \
|
||||
--device model=sailfish,version=25 \
|
||||
--device model=harpia,version=23 \
|
||||
--timeout 15m
|
||||
|
|
22
.github/workflows/android-release.yaml
vendored
22
.github/workflows/android-release.yaml
vendored
|
@ -29,17 +29,15 @@ jobs:
|
|||
version=$(tools/unix/version.sh ios_version)
|
||||
# +1 because below a "Bump versions" commit is created.
|
||||
# TODO: Find a way to refactor FDroid versioning without that additional commit.
|
||||
# build=$(($(tools/unix/version.sh ios_build) + 1))
|
||||
# code=$(($(tools/unix/version.sh android_code) + 1))
|
||||
build=$(tools/unix/version.sh ios_build)
|
||||
code=$(tools/unix/version.sh android_code)
|
||||
build=$(($(tools/unix/version.sh ios_build) + 1))
|
||||
code=$(($(tools/unix/version.sh android_code) + 1))
|
||||
tag=$version-$build-android
|
||||
echo "::set-output name=version::$version"
|
||||
echo "::set-output name=build::$build"
|
||||
echo "::set-output name=tag::$tag"
|
||||
echo "::set-output name=code::$code"
|
||||
# echo "version: ${version}-${build}-FDroid+${code}" > ${{ env.FDROID_VERSION }}
|
||||
# git add ${{ env.FDROID_VERSION }}
|
||||
echo "version: ${version}-${build}-FDroid+${code}" > ${{ env.FDROID_VERSION }}
|
||||
git add ${{ env.FDROID_VERSION }}
|
||||
{
|
||||
echo $tag
|
||||
echo
|
||||
|
@ -47,7 +45,7 @@ jobs:
|
|||
} > ${{ runner.temp }}/tag.txt
|
||||
branch="${GITHUB_REF#refs/heads/}"
|
||||
test -n "$branch"
|
||||
# git commit -m "Bump versions" -s
|
||||
git commit -m "Bump versions" -s
|
||||
git tag -a $tag -F ${{ runner.temp }}/tag.txt
|
||||
git show $tag
|
||||
git push origin $branch:$branch
|
||||
|
@ -118,7 +116,7 @@ jobs:
|
|||
|
||||
- name: Set up SDK
|
||||
shell: bash
|
||||
run: (cd tools/android; ./set_up_android.py --sdk $ANDROID_SDK_ROOT)
|
||||
run: echo "sdk.dir=$ANDROID_SDK_ROOT" > android/local.properties
|
||||
|
||||
- name: Compile and upload to Google Play
|
||||
shell: bash
|
||||
|
@ -146,6 +144,7 @@ jobs:
|
|||
if: ${{ matrix.flavor == 'web' }}
|
||||
shell: bash
|
||||
run: |
|
||||
(cd ./android/app/build/outputs/apk/web/release/ && sha256sum OrganicMaps-${{ needs.tag.outputs.code }}-web-release.apk > OrganicMaps-${{ needs.tag.outputs.code }}-web-release.apk.sha256sum)
|
||||
{
|
||||
cat ${{ env.RELEASE_NOTES }}
|
||||
echo ""
|
||||
|
@ -153,7 +152,7 @@ jobs:
|
|||
echo ""
|
||||
echo "sha256sum:"
|
||||
echo -e '\n```'
|
||||
(cd ./android/app/build/outputs/apk/web/release/ && sha256sum OrganicMaps-${{ needs.tag.outputs.code }}-web-release.apk) | tr -d '\n'
|
||||
tr -d '\n' < ./android/app/build/outputs/apk/web/release/OrganicMaps-${{ needs.tag.outputs.code }}-web-release.apk.sha256sum
|
||||
echo -e '\n```'
|
||||
} > ${{ runner.temp }}/release-notes.txt
|
||||
|
||||
|
@ -166,5 +165,8 @@ jobs:
|
|||
name: ${{ needs.tag.outputs.tag }}
|
||||
tag_name: ${{ needs.tag.outputs.tag }}
|
||||
discussion_category_name: 'Announcements'
|
||||
files: ./android/app/build/outputs/apk/web/release/OrganicMaps-${{ needs.tag.outputs.code }}-web-release.apk
|
||||
prerelease: true
|
||||
files: |
|
||||
./android/app/build/outputs/apk/web/release/OrganicMaps-${{ needs.tag.outputs.code }}-web-release.apk
|
||||
./android/app/build/outputs/apk/web/release/OrganicMaps-${{ needs.tag.outputs.code }}-web-release.apk.sha256sum
|
||||
fail_on_unmatched_files: true
|
||||
|
|
11
.github/workflows/appstream-check.yaml
vendored
11
.github/workflows/appstream-check.yaml
vendored
|
@ -9,21 +9,24 @@ on:
|
|||
jobs:
|
||||
validate-appstream:
|
||||
name: Validate appstream metadata xml
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install appstream validator
|
||||
- name: Install appstream validator and flatpak Builder
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt update -y
|
||||
sudo apt install -y \
|
||||
flatpak
|
||||
sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
# We get it from flathub to ensure we have a recent version
|
||||
sudo flatpak install -y org.freedesktop.appstream-glib
|
||||
sudo flatpak install -y org.freedesktop.appstream-glib org.flatpak.Builder
|
||||
|
||||
- name: Validate appstream data
|
||||
shell: bash
|
||||
run: flatpak run org.freedesktop.appstream-glib validate --nonet packaging/app.organicmaps.desktop.metainfo.xml
|
||||
|
||||
- name: Lint appstream data with flatpak Builder
|
||||
shell: bash
|
||||
run: flatpak run --command=flatpak-builder-lint org.flatpak.Builder appstream packaging/app.organicmaps.desktop.metainfo.xml
|
||||
|
|
25
.github/workflows/coverage-check.yaml
vendored
25
.github/workflows/coverage-check.yaml
vendored
|
@ -7,6 +7,23 @@ on:
|
|||
- synchronize
|
||||
- labeled
|
||||
- unlabeled
|
||||
paths-ignore:
|
||||
- .gitignore
|
||||
- CONTRIBUTORS
|
||||
- LICENSE
|
||||
- NOTICE
|
||||
- README.md
|
||||
- docs/**
|
||||
- packaging/**
|
||||
- platform/*apple*
|
||||
- platform/*_android*
|
||||
- platform/*_ios*
|
||||
- platform/*_mac*
|
||||
- platform/*_win*
|
||||
- pyhelpers/**
|
||||
- tools/**
|
||||
- '!tools/python/test_server/**'
|
||||
- xcode/**
|
||||
|
||||
# Cancels previous jobs if the same branch or PR was updated again.
|
||||
concurrency:
|
||||
|
@ -16,7 +33,7 @@ concurrency:
|
|||
jobs:
|
||||
should-run-check:
|
||||
name: Should run coverage
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
run-from-pr: ${{ steps.run-from-pr.outputs.run-from-pr }}
|
||||
manually-triggered: ${{ steps.manually-triggered.outputs.manually-triggered }}
|
||||
|
@ -42,7 +59,7 @@ jobs:
|
|||
coverage:
|
||||
needs: should-run-check
|
||||
name: Generate coverage report
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ needs.should-run-check.outputs.run-from-pr == 'true' || needs.should-run-check.outputs.manually-triggered == 'true'}}
|
||||
steps:
|
||||
- name: Free disk space by removing .NET, Android and Haskell
|
||||
|
@ -87,8 +104,8 @@ jobs:
|
|||
- name: CMake
|
||||
shell: bash
|
||||
env:
|
||||
CC: clang-14
|
||||
CXX: clang++-14
|
||||
CC: clang-18
|
||||
CXX: clang++-18
|
||||
CMAKE_C_COMPILER_LAUNCHER: ccache
|
||||
CMAKE_CXX_COMPILER_LAUNCHER: ccache
|
||||
# -g1 should slightly reduce build time.
|
||||
|
|
14
.github/workflows/ios-beta.yaml
vendored
14
.github/workflows/ios-beta.yaml
vendored
|
@ -20,10 +20,10 @@ on:
|
|||
- generator/**
|
||||
- packaging/**
|
||||
- platform/*_android*
|
||||
- platform/*_linux*
|
||||
- platform/*_mac*
|
||||
- platform/*qt*
|
||||
- platform/*_win*
|
||||
- platform/*_linux*
|
||||
- platform/*_mac*
|
||||
- platform/*qt*
|
||||
- platform/*_win*
|
||||
- pyhelpers/**
|
||||
- qt*/**
|
||||
- skin_generator/**
|
||||
|
@ -33,9 +33,9 @@ on:
|
|||
jobs:
|
||||
ios-beta:
|
||||
name: Apple TestFlight
|
||||
runs-on: macos-13
|
||||
runs-on: macos-14
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_15.0.1.app/Contents/Developer
|
||||
DEVELOPER_DIR: /Applications/Xcode_15.3.app/Contents/Developer
|
||||
LANG: en_US.UTF-8 # Fastlane complains that the terminal is using ASCII.
|
||||
LANGUAGE: en_US.UTF-8
|
||||
LC_ALL: en_US.UTF-8
|
||||
|
@ -67,7 +67,7 @@ jobs:
|
|||
|
||||
- name: Compile and upload to TestFlight
|
||||
run: |
|
||||
echo "IOS_VERSION=$(tools/unix/version.sh ios_version)-$(tools/unix/version.sh ios_build)" >> "$GITHUB_ENV"
|
||||
echo "IOS_VERSION=$(../tools/unix/version.sh ios_version)-$(../tools/unix/version.sh ios_build)" >> "$GITHUB_ENV"
|
||||
./fastlane.sh upload_testflight
|
||||
env:
|
||||
APPSTORE_CERTIFICATE_PASSWORD: '${{ secrets.APPSTORE_CERTIFICATE_PASSWORD }}'
|
||||
|
|
55
.github/workflows/ios-check.yaml
vendored
55
.github/workflows/ios-check.yaml
vendored
|
@ -22,10 +22,10 @@ on:
|
|||
- iphone/metadata/**
|
||||
- packaging/**
|
||||
- platform/*_android*
|
||||
- platform/*_linux*
|
||||
- platform/*_mac*
|
||||
- platform/*qt*
|
||||
- platform/*_win*
|
||||
- platform/*_linux*
|
||||
- platform/*_mac*
|
||||
- platform/*qt*
|
||||
- platform/*_win*
|
||||
- pyhelpers/**
|
||||
- qt*/**
|
||||
- skin_generator/**
|
||||
|
@ -35,12 +35,13 @@ on:
|
|||
jobs:
|
||||
ios-check:
|
||||
name: Build iOS
|
||||
runs-on: macos-13
|
||||
runs-on: macos-14
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_15.0.1.app/Contents/Developer
|
||||
DEVELOPER_DIR: /Applications/Xcode_15.3.app/Contents/Developer
|
||||
LANG: en_US.UTF-8 # Fastlane complains that the terminal is using ASCII.
|
||||
LANGUAGE: en_US.UTF-8
|
||||
LC_ALL: en_US.UTF-8
|
||||
TEST_RESULTS_BUNDLE_NAME: OMaps-Test-Results
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -62,20 +63,44 @@ jobs:
|
|||
shell: bash
|
||||
run: ./configure.sh
|
||||
|
||||
- name: Configure ccache
|
||||
uses: mikehardy/buildcache-action@v2.1.0
|
||||
- name: Configure XCode cache
|
||||
uses: irgaly/xcode-cache@v1
|
||||
with:
|
||||
cache_key: ${{ github.workflow }}-${{ matrix.buildType }}
|
||||
key: xcode-cache-deriveddata-${{ github.workflow }}-${{ matrix.buildType }}-${{ github.sha }}
|
||||
restore-keys: xcode-cache-deriveddata-${{ github.workflow }}-${{ matrix.buildType }}
|
||||
|
||||
- name: Compile
|
||||
- name: Build and Run Tests (Debug)
|
||||
if: matrix.buildType == 'Debug'
|
||||
shell: bash
|
||||
# Check for compilation errors.
|
||||
run: |
|
||||
xcodebuild \
|
||||
CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ \
|
||||
xcodebuild test \
|
||||
-workspace xcode/omim.xcworkspace \
|
||||
-scheme OMaps \
|
||||
-configuration ${{ matrix.buildType }} build \
|
||||
'generic/platform=iOS' \
|
||||
-configuration Debug \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 15,OS=latest' \
|
||||
-quiet \
|
||||
-resultBundlePath ${{ env.TEST_RESULTS_BUNDLE_NAME }}.xcresult \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGNING_ALLOWED=NO
|
||||
|
||||
- name: Upload Test Results On Failure (Debug)
|
||||
if: ${{ matrix.buildType == 'Debug' && failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.TEST_RESULTS_BUNDLE_NAME }}-${{ github.run_number }}.xcresult
|
||||
path: ${{ env.TEST_RESULTS_BUNDLE_NAME }}.xcresult
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Build (Release)
|
||||
if: matrix.buildType == 'Release'
|
||||
shell: bash
|
||||
run: |
|
||||
xcodebuild build \
|
||||
-workspace xcode/omim.xcworkspace \
|
||||
-scheme OMaps \
|
||||
-configuration Release \
|
||||
-destination 'generic/platform=iOS' \
|
||||
-quiet \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGNING_ALLOWED=NO
|
4
.github/workflows/ios-release.yaml
vendored
4
.github/workflows/ios-release.yaml
vendored
|
@ -5,9 +5,9 @@ on:
|
|||
jobs:
|
||||
ios-release:
|
||||
name: iOS Release
|
||||
runs-on: macos-13
|
||||
runs-on: macos-14
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_15.0.1.app/Contents/Developer
|
||||
DEVELOPER_DIR: /Applications/Xcode_15.3.app/Contents/Developer
|
||||
LANG: en_US.UTF-8 # Fastlane complains that the terminal is using ASCII.
|
||||
LANGUAGE: en_US.UTF-8
|
||||
LC_ALL: en_US.UTF-8
|
||||
|
|
21
.github/workflows/linux-check.yaml
vendored
21
.github/workflows/linux-check.yaml
vendored
|
@ -20,9 +20,9 @@ on:
|
|||
- packaging/**
|
||||
- platform/*apple*
|
||||
- platform/*_android*
|
||||
- platform/*_ios*
|
||||
- platform/*_mac*
|
||||
- platform/*_win*
|
||||
- platform/*_ios*
|
||||
- platform/*_mac*
|
||||
- platform/*_win*
|
||||
- pyhelpers/**
|
||||
- tools/**
|
||||
- '!tools/python/test_server/**'
|
||||
|
@ -31,7 +31,7 @@ on:
|
|||
jobs:
|
||||
linux-no-unity:
|
||||
name: Linux no unity build
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
# Cancels previous jobs if the same branch or PR was updated again.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-no-unity-${{ github.event.pull_request.number || github.ref }}
|
||||
|
@ -60,6 +60,7 @@ jobs:
|
|||
ninja-build \
|
||||
libgl1-mesa-dev \
|
||||
libglvnd-dev \
|
||||
libharfbuzz-dev \
|
||||
qt6-base-dev \
|
||||
libqt6svg6-dev \
|
||||
qt6-positioning-dev \
|
||||
|
@ -78,8 +79,8 @@ jobs:
|
|||
- name: CMake
|
||||
shell: bash
|
||||
env:
|
||||
CC: clang-14
|
||||
CXX: clang++-14
|
||||
CC: clang-18
|
||||
CXX: clang++-18
|
||||
CMAKE_C_COMPILER_LAUNCHER: ccache
|
||||
CMAKE_CXX_COMPILER_LAUNCHER: ccache
|
||||
# -g1 should slightly reduce build time.
|
||||
|
@ -94,11 +95,11 @@ jobs:
|
|||
|
||||
linux-matrix:
|
||||
name: Linux builds and tests
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [{ CXX: g++-12, CC: gcc-12 }, { CXX: clang++-14, CC: clang-14 }]
|
||||
compiler: [{ CXX: g++-14, CC: gcc-14 }, { CXX: clang++-18, CC: clang-18 }]
|
||||
CMAKE_BUILD_TYPE: [Debug, RelWithDebInfo]
|
||||
# Cancels previous jobs if the same branch or PR was updated again.
|
||||
concurrency:
|
||||
|
@ -123,11 +124,10 @@ jobs:
|
|||
run: |
|
||||
sudo apt update -y
|
||||
sudo apt install -y \
|
||||
g++-12 \
|
||||
gcc-12 \
|
||||
ninja-build \
|
||||
libgl1-mesa-dev \
|
||||
libglvnd-dev \
|
||||
libharfbuzz-dev \
|
||||
qt6-base-dev \
|
||||
libqt6svg6-dev \
|
||||
qt6-positioning-dev \
|
||||
|
@ -166,6 +166,7 @@ jobs:
|
|||
working-directory: build
|
||||
env:
|
||||
# drape_tests - requires X Window
|
||||
QT_QPA_PLATFORM: "offscreen"
|
||||
# generator_integration_tests - https://github.com/organicmaps/organicmaps/issues/225
|
||||
# opening_hours_integration_tests - https://github.com/organicmaps/organicmaps/issues/219
|
||||
# opening_hours_supported_features_tests - https://github.com/organicmaps/organicmaps/issues/219
|
||||
|
|
8
.github/workflows/macos-check.yaml
vendored
8
.github/workflows/macos-check.yaml
vendored
|
@ -20,8 +20,8 @@ on:
|
|||
- packaging/**
|
||||
- platform/*_android*
|
||||
- platform/*_ios*
|
||||
- platform/*_linux*
|
||||
- platform/*_win*
|
||||
- platform/*_linux*
|
||||
- platform/*_win*
|
||||
- pyhelpers/**
|
||||
- tools/**
|
||||
- '!tools/python/test_server/**'
|
||||
|
@ -30,9 +30,9 @@ on:
|
|||
jobs:
|
||||
macos-matrix:
|
||||
name: macOS builds and tests
|
||||
runs-on: macos-13
|
||||
runs-on: macos-14
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_15.0.1.app/Contents/Developer
|
||||
DEVELOPER_DIR: /Applications/Xcode_15.3.app/Contents/Developer
|
||||
HOMEBREW_NO_ANALYTICS: 1
|
||||
HOMEBREW_NO_INSTALL_CLEANUP: 1
|
||||
strategy:
|
||||
|
|
15
.github/workflows/strings-check.yaml
vendored
15
.github/workflows/strings-check.yaml
vendored
|
@ -4,8 +4,11 @@ on:
|
|||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/strings-check.yaml # Run check on self change
|
||||
- data/strings/strings.txt
|
||||
- data/strings/types_strings.txt
|
||||
- data/strings/sound.txt
|
||||
- data/countries_names.txt
|
||||
- data/strings/*
|
||||
- iphone/plist.txt
|
||||
- tools/python/strings_utils.py
|
||||
|
||||
jobs:
|
||||
|
@ -15,12 +18,14 @@ jobs:
|
|||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3'
|
||||
|
||||
- name: Validate strings.txt and types_strings.txt files
|
||||
- name: Validate string files
|
||||
shell: bash
|
||||
run: |
|
||||
./tools/python/strings_utils.py --validate
|
||||
./tools/python/strings_utils.py --types-strings --validate
|
||||
for f in data/strings/strings.txt data/strings/types_strings.txt data/strings/sound.txt data/countries_names.txt iphone/plist.txt; do
|
||||
./tools/python/strings_utils.py --validate $f -o
|
||||
done;
|
||||
git diff --exit-code
|
||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -123,6 +123,7 @@ tizen/*/.*
|
|||
tizen/*/crash-info/*
|
||||
.idea/*
|
||||
.idea
|
||||
!android/.idea/icon.svg
|
||||
|
||||
# Private repository files.
|
||||
.private_repository_url
|
||||
|
@ -175,6 +176,9 @@ tools/python/routing/etc/*.ini
|
|||
/node_modules/
|
||||
/package-lock.json
|
||||
|
||||
# Visual Studio
|
||||
.vs
|
||||
|
||||
# VS Code
|
||||
.vscode
|
||||
|
||||
|
|
|
@ -2,14 +2,15 @@
|
|||
# Compatibility with CMake < 3.5 will be removed from a future version of CMake.
|
||||
set(CMAKE_WARN_DEPRECATED OFF CACHE BOOL "" FORCE)
|
||||
|
||||
if (WITH_SYSTEM_PROVIDED_3PARTY)
|
||||
set(GFLAGS_USE_TARGET_NAMESPACE ON)
|
||||
find_package(gflags REQUIRED GLOBAL)
|
||||
else()
|
||||
# Configure expat library.
|
||||
if (NOT WITH_SYSTEM_PROVIDED_3PARTY)
|
||||
# Suppress "Policy CMP0077 is not set: option() honors normal variables"
|
||||
# for the expat options below.
|
||||
# for the freetype, expat and jansson options.
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
|
||||
# Suppress "Policy CMP0063 is not set: Honor visibility properties for all target types."
|
||||
# for jansson
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0063 NEW)
|
||||
|
||||
# Configure expat library.
|
||||
set(EXPAT_BUILD_TOOLS OFF)
|
||||
set(EXPAT_BUILD_EXAMPLES OFF)
|
||||
set(EXPAT_BUILD_TESTS OFF)
|
||||
|
@ -17,7 +18,11 @@ else()
|
|||
set(EXPAT_BUILD_PKGCONFIG OFF)
|
||||
set(EXPAT_ENABLE_INSTALL OFF)
|
||||
set(EXPAT_SHARED_LIBS OFF)
|
||||
set(EXPAT_GE OFF)
|
||||
set(EXPAT_DTD OFF)
|
||||
set(EXPAT_NS ON)
|
||||
add_subdirectory(expat/expat)
|
||||
add_library(expat::expat ALIAS expat)
|
||||
|
||||
# Configure Jansson library.
|
||||
set(JANSSON_BUILD_DOCS OFF)
|
||||
|
@ -27,6 +32,7 @@ else()
|
|||
set(JANSSON_WITHOUT_TESTS ON)
|
||||
add_subdirectory(jansson/jansson/)
|
||||
target_include_directories(jansson INTERFACE "${PROJECT_BINARY_DIR}/3party/jansson/jansson/include")
|
||||
add_library(jansson::jansson ALIAS jansson)
|
||||
|
||||
# Add gflags library.
|
||||
add_subdirectory(gflags)
|
||||
|
@ -37,22 +43,23 @@ else()
|
|||
|
||||
# Add protobuf library.
|
||||
add_subdirectory(protobuf)
|
||||
|
||||
if (NOT PLATFORM_LINUX)
|
||||
add_subdirectory(freetype)
|
||||
add_subdirectory(icu)
|
||||
add_subdirectory(harfbuzz)
|
||||
endif()
|
||||
|
||||
add_library(utf8cpp INTERFACE)
|
||||
add_library(utf8cpp::utf8cpp ALIAS utf8cpp)
|
||||
target_include_directories(utf8cpp INTERFACE "${OMIM_ROOT}/3party/utfcpp/source")
|
||||
endif()
|
||||
|
||||
add_subdirectory(agg)
|
||||
add_subdirectory(bsdiff-courgette)
|
||||
|
||||
if (NOT LINUX_DETECTED)
|
||||
add_subdirectory(freetype)
|
||||
add_subdirectory(icu)
|
||||
endif()
|
||||
|
||||
add_subdirectory(harfbuzz)
|
||||
add_subdirectory(liboauthcpp)
|
||||
add_subdirectory(minizip)
|
||||
add_subdirectory(open-location-code)
|
||||
add_subdirectory(opening_hours)
|
||||
add_subdirectory(sdf_image)
|
||||
add_subdirectory(stb_image)
|
||||
add_subdirectory(succinct)
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 83e1a9ed8ce289cebb1c02c8167d663dc1befb24
|
||||
Subproject commit 31aa7f634b052d87ede4664053e85f3f4d1d50d3
|
|
@ -1 +1 @@
|
|||
Subproject commit 654d2de0da85662fcc7644a7acd7c2dd2cfb21f0
|
||||
Subproject commit 9cbdb916de2a7bd1aa649e55efc38d2426680359
|
|
@ -1 +1 @@
|
|||
Subproject commit e4586d960f339cf75e2e0b34aee30a0ed8353c0d
|
||||
Subproject commit 920c5502cc3ddda88f6c7d85ee834ac611bb11cc
|
|
@ -1 +1 @@
|
|||
Subproject commit e8eb1dc5ff695427abc137d3d15c4eec64ab6c78
|
||||
Subproject commit 2da79f70a1d562d883bdde5b74f6603374fb7023
|
|
@ -1 +1 @@
|
|||
Subproject commit e23f5580072cb64ce3ab27de2b5110d7ac252424
|
||||
Subproject commit 61fc3d0e28e1a35410af42e329cd977095ec32d2
|
9
3party/liboauthcpp/.gitignore
vendored
9
3party/liboauthcpp/.gitignore
vendored
|
@ -1,9 +0,0 @@
|
|||
# Build (by)products
|
||||
build/CMakeCache.txt
|
||||
build/CMakeFiles
|
||||
build/Makefile
|
||||
build/cmake_install.cmake
|
||||
build/liboauthcpp.a
|
||||
build/simple_auth
|
||||
build/simple_request
|
||||
build/tests
|
|
@ -1,21 +0,0 @@
|
|||
project(oauthcpp)
|
||||
|
||||
set(SRC
|
||||
include/liboauthcpp/liboauthcpp.h
|
||||
src/base64.cpp
|
||||
src/HMAC_SHA1.cpp
|
||||
src/SHA1.cpp
|
||||
src/urlencode.cpp
|
||||
src/liboauthcpp.cpp
|
||||
)
|
||||
|
||||
add_library(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PRIVATE src
|
||||
PUBLIC include
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME}
|
||||
PRIVATE $<$<CXX_COMPILER_ID:AppleClang,Clang>:-Wno-shorten-64-to-32>
|
||||
)
|
|
@ -1,21 +0,0 @@
|
|||
Copyright (c) 2011 Stanford University (liboauthcpp)
|
||||
Copyright (C) 2011 by swatkat (swatkat.thinkdigitATgmailDOTcom) (libtwitcurl)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -1,172 +0,0 @@
|
|||
liboauthcpp
|
||||
-----------
|
||||
|
||||
liboauthcpp is a pure C++ library for performing OAuth requests. It
|
||||
doesn't contain any networking code -- you provide for performing HTTP
|
||||
requests yourself, however you like -- instead focusing on performing
|
||||
OAuth-specific functionality and providing a nice interface for it.
|
||||
If you already have infrastructure for making HTTP requests and are
|
||||
looking to add OAuth support, liboauthcpp is for you.
|
||||
|
||||
liboauthcpp currently implements OAuth 1.0a (see
|
||||
http://tools.ietf.org/html/rfc5849).
|
||||
|
||||
Buildbot
|
||||
--------
|
||||
[](http://travis-ci.org/sirikata/liboauthcpp)
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
You should only need:
|
||||
|
||||
* CMake
|
||||
* A C++ compiler for your platform (e.g. g++, Microsoft Visual C++)
|
||||
|
||||
Compiling
|
||||
---------
|
||||
|
||||
The build process is simple:
|
||||
|
||||
cd liboauthcpp
|
||||
cd build
|
||||
cmake .
|
||||
make # or open Visual Studio and build the solution
|
||||
|
||||
If your own project uses CMake you can also include
|
||||
build/CMakeLists.txt directly into your project and reference the
|
||||
target "oauthcpp", a static library, in your project.
|
||||
|
||||
Percent (URL) Encoding
|
||||
----------------------
|
||||
|
||||
To get correct results, you need to pass your URL properly encoded to
|
||||
liboauthcpp. If you are not at all familiar, you should probably start
|
||||
by reading the [URI Spec](http://tools.ietf.org/html/rfc3986), especially
|
||||
Section 2. Alternatively,
|
||||
[this article](http://blog.lunatech.com/2009/02/03/what-every-web-developer-must-know-about-url-encoding)
|
||||
gives a more readable overview.
|
||||
|
||||
The basic idea is that there are 3 classes of characters: reserved,
|
||||
unreserved, and other. Reserved characters are special characters that
|
||||
are used in the URI syntax itself, e.g. ':' (after the scheme), '/'
|
||||
(the hierarchical path separator), and '?' (prefixing the query
|
||||
string). Unreserved characters are characters that are always safe to
|
||||
include unencoded, e.g. the alphanumerics. Other characters must
|
||||
always be encoded, mainly covering special characters like ' ', '<' or
|
||||
'>', and '{' or '}'.
|
||||
|
||||
The basic rule is that reserved characters must be encoded if they
|
||||
appear in any part of the URI when not being used as a
|
||||
separator. Unreserved characters are always safe. And the other
|
||||
characters they didn't know if they would be safe or not so they must
|
||||
always be encoded.
|
||||
|
||||
Unfortunately, the reserved set is a bit more complicated. They are
|
||||
broken down into 'general delimiters' and 'sub delimiters'. The ones
|
||||
already mentioned, like ':', can appear in many forms of URIs (say,
|
||||
http, ftp, about, gopher, mailto, etc. Those are called general
|
||||
delimiters. Others (e.g. '(', ')', '!', '$', '+', ',', '=', and more)
|
||||
are called subdelimiters because their use depends on the URI
|
||||
scheme. Worse, their use depends on the *part of the URI*. Depending
|
||||
on the particular URI scheme, these may or may not have to be encoded,
|
||||
and it might also depend on where they appear. (As an example, an '&'
|
||||
in an http URI isn't an issue if it appears in the path -- before the
|
||||
query string -- i.e. before a '?' appears. Worse, '=' can appear unencoded in
|
||||
the path, or in a query parameter value, but not in a query parameter key since
|
||||
it would be interpreted as the end of the key.)
|
||||
|
||||
*Additionally*, in many cases it is permitted to encode a character
|
||||
unnecessarily and the result is supposed to be the same. This means
|
||||
that it's possible to percent encode some URLs in multiple ways
|
||||
(e.g. encoding the unreserved set unnecessarily). It is possible, but not
|
||||
guaranteed, that if you pass *exactly* the same URI to liboauthcpp and the
|
||||
OAuth server, it will handle it regardless of the variant of encoding, so long
|
||||
as it is a valid encoding.
|
||||
|
||||
The short version: percent encoding a URL properly is non-trivial and
|
||||
you can even encode the same URL multiple ways, but has to be done
|
||||
correctly so that the OAuth signature can be computed. Sadly,
|
||||
"correctly" in this case really means "in whatever way the server your
|
||||
interacting with wants it encoded".
|
||||
|
||||
Internally, liboauthcpp needs to do another step of percent encoding,
|
||||
but the OAuth spec is very precise about how that works (none of these
|
||||
scheme-dependent issues). liboauth applies this percent encoding, but
|
||||
assumes that you have encoded your URLs properly. This assumption
|
||||
makes sense since the actual request is made separately, and the URI
|
||||
has to be specified in it, so you should already have a form which the
|
||||
server will accept.
|
||||
|
||||
However, in order to aid you, a very simple percent encoding API is exposed. It
|
||||
should help you encode URLs minimally and in a way that many services accept. In
|
||||
most cases you should use `HttpPercentEncodePath()`,
|
||||
`HttpPercentEncodeQueryKey()`, and `HttpPercentEncodeQueryValue()` to encode
|
||||
those parts of your http URL, then combine them and pass them to liboauthcpp for
|
||||
signing.
|
||||
|
||||
|
||||
Thread Safety
|
||||
-------------
|
||||
|
||||
liboauthcpp doesn't provide any thread safety guarantees. That said, there is
|
||||
very little shared state, and some classes (e.g. Consumer) are naturally
|
||||
immutable and therefore thread safe. Similarly, nearly the entire library uses
|
||||
no static/shared state, so as long as you create separate objects for separate
|
||||
threads, you should be safe.
|
||||
|
||||
The one exception is nonces: the Client class needs to generate a nonce for
|
||||
authorization. To do so, the random number generator needs to be seeded. We do
|
||||
this with the current time, but fast, repeated use of the Client class from
|
||||
different threads could result in the same nonce. To avoid requiring an entire
|
||||
thread library just for this one case, you can call Client::initialize()
|
||||
explicitly before using the Client from multiple threads. For single-threaded
|
||||
use, you are not required to call it.
|
||||
|
||||
Demos
|
||||
-----
|
||||
There are two demos included in the demos/ directory, and they are built by
|
||||
default with the instructions above. In both, you enter key/secret information
|
||||
and it generates URLs for you to visit (in a browser) and copy data back into
|
||||
the program.
|
||||
|
||||
simple_auth should be executed first. It starts with only a consumer key and
|
||||
secret and performs 3-legged auth: you enter in consumer keys, it generates URLs
|
||||
to authenticate the user and generate access tokens. It requires 3 steps:
|
||||
request_token, authorize, and access_token (which correspond the URLs
|
||||
accessed). At the end of this process, you'll be provided an access key/secret
|
||||
pair which you can use to access actual resources.
|
||||
|
||||
simple_request actually does something useful now that your application is
|
||||
authorized. Enter your consumer key/secret and the access key/secret from
|
||||
simple_auth (or which you've generated elsewhere) and it will generate a URL you
|
||||
can use to access your home timeline in JSON format. It adds a parameter to ask
|
||||
for only 5 entries (demonstrating that signing works properly over additional
|
||||
query parameters). This is a one-step process -- it just gives you the URL and
|
||||
you get the results in your browser.
|
||||
|
||||
In both, the URLs accessed are specified at the top of the demo
|
||||
files. simple_auth requires URLs for request_token, authorize_url, and
|
||||
access_token. Some providers require additional parameters (notably an
|
||||
oauth_callback for Twitter, even if its out of band, or oob), which you can also
|
||||
specify in that location. simple_request only needs the URL of the resource
|
||||
being accessed (i.e. the URL for the home_timeline JSON data used by default in
|
||||
the demo), with optional parameters stored as a query string.
|
||||
|
||||
Both demos only use GET requests with query strings, but all HTTP methods
|
||||
(e.g. POST, PUT, DELETE) and approaches to sending parameters (e.g. HTTP
|
||||
headers, url-encoded body) should be supported in the API.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
liboauthcpp is MIT licensed. See the LICENSE file for more details.
|
||||
|
||||
liboauthcpp is mostly taken from libtwitcurl
|
||||
(http://code.google.com/p/twitcurl/), which is similarly licensed. It
|
||||
mostly serves to isolate the OAuth code from libtwitcurl's Twitter and
|
||||
cURL specific code.
|
||||
|
||||
libtwitcurl also borrowed code from other projects:
|
||||
twitcurl uses HMAC_SHA1 from http://www.codeproject.com/KB/recipes/HMACSHA1class.aspx
|
||||
twitcurl uses base64 from http://www.adp-gmbh.ch/cpp/common/base64.html
|
|
@ -1,286 +0,0 @@
|
|||
#ifndef __LIBOAUTHCPP_LIBOAUTHCPP_H__
|
||||
#define __LIBOAUTHCPP_LIBOAUTHCPP_H__
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <ctime>
|
||||
|
||||
namespace OAuth {
|
||||
|
||||
namespace Http {
|
||||
typedef enum _RequestType
|
||||
{
|
||||
Invalid = 0,
|
||||
Head,
|
||||
Get,
|
||||
Post,
|
||||
Delete,
|
||||
Put
|
||||
} RequestType;
|
||||
} // namespace Http
|
||||
|
||||
typedef std::list<std::string> KeyValueList;
|
||||
typedef std::multimap<std::string, std::string> KeyValuePairs;
|
||||
|
||||
typedef enum _LogLevel
|
||||
{
|
||||
LogLevelNone = 0,
|
||||
LogLevelDebug = 1
|
||||
} LogLevel;
|
||||
|
||||
/** Set the log level. Log messages are sent to stderr. Currently, and for the
|
||||
* foreseeable future, logging only consists of debug messages to help track
|
||||
* down protocol implementation issues.
|
||||
*/
|
||||
void SetLogLevel(LogLevel lvl);
|
||||
|
||||
/** Deprecated. Complete percent encoding of URLs. Equivalent to
|
||||
* PercentEncode.
|
||||
*/
|
||||
std::string URLEncode(const std::string& decoded);
|
||||
|
||||
/** Percent encode a string value. This version is *thorough* about
|
||||
* encoding: it encodes all reserved characters (even those safe in
|
||||
* http URLs) and "other" characters not specified by the URI
|
||||
* spec. If you're looking to encode http:// URLs, see the
|
||||
* HttpEncode* functions.
|
||||
*/
|
||||
std::string PercentEncode(const std::string& decoded);
|
||||
|
||||
/** Percent encodes the path portion of an http URL (i.e. the /foo/bar
|
||||
* in http://foo/bar?a=1&b=2). This encodes minimally, so reserved
|
||||
* subdelimiters that have no meaning in the path are *not* encoded.
|
||||
*/
|
||||
std::string HttpEncodePath(const std::string& decoded);
|
||||
|
||||
/** Percent encodes a query string key in an http URL (i.e. 'a', 'b' in
|
||||
* http://foo/bar?a=1&b=2). This encodes minimally, so reserved subdelimiters
|
||||
* that have no meaning in the query string are *not* encoded.
|
||||
*/
|
||||
std::string HttpEncodeQueryKey(const std::string& decoded);
|
||||
|
||||
/** Percent encodes a query string value in an http URL (i.e. '1', '2' in
|
||||
* http://foo/bar?a=1&b=2). This encodes minimally, so reserved subdelimiters
|
||||
* that have no meaning in the query string are *not* encoded.
|
||||
*/
|
||||
std::string HttpEncodeQueryValue(const std::string& decoded);
|
||||
|
||||
/** Parses key value pairs into a map.
|
||||
* \param encoded the encoded key value pairs, i.e. the url encoded parameters
|
||||
* \returns a map of string keys to string values
|
||||
* \throws ParseError if the encoded data cannot be decoded
|
||||
*/
|
||||
KeyValuePairs ParseKeyValuePairs(const std::string& encoded);
|
||||
|
||||
class ParseError : public std::runtime_error {
|
||||
public:
|
||||
ParseError(const std::string msg)
|
||||
: std::runtime_error(msg)
|
||||
{}
|
||||
};
|
||||
|
||||
class MissingKeyError : public std::runtime_error {
|
||||
public:
|
||||
MissingKeyError(const std::string msg)
|
||||
: std::runtime_error(msg)
|
||||
{}
|
||||
};
|
||||
|
||||
/** A consumer of OAuth-protected services. It is the client to an
|
||||
* OAuth service provider and is usually registered with the service
|
||||
* provider, resulting in a consumer *key* and *secret* used to
|
||||
* identify the consumer. The key is included in all requests and the
|
||||
* secret is used to *sign* all requests. Signed requests allow the
|
||||
* consumer to securely perform operations, including kicking off
|
||||
* three-legged authentication to enable performing operations on
|
||||
* behalf of a user of the service provider.
|
||||
*/
|
||||
class Consumer {
|
||||
public:
|
||||
Consumer(const std::string& key, const std::string& secret);
|
||||
|
||||
const std::string& key() const { return mKey; }
|
||||
const std::string& secret() const { return mSecret; }
|
||||
|
||||
private:
|
||||
const std::string mKey;
|
||||
const std::string mSecret;
|
||||
};
|
||||
|
||||
/** An OAuth credential used to request authorization or a protected
|
||||
* resource.
|
||||
*
|
||||
* Tokens in OAuth comprise a *key* and a *secret*. The key is
|
||||
* included in requests to identify the token being used, but the
|
||||
* secret is used only in the signature, to prove that the requester
|
||||
* is who the server gave the token to.
|
||||
*
|
||||
* When first negotiating the authorization, the consumer asks for a
|
||||
* *request token* that the live user authorizes with the service
|
||||
* provider. The consumer then exchanges the request token for an
|
||||
* *access token* that can be used to access protected resources.
|
||||
*/
|
||||
class Token {
|
||||
public:
|
||||
Token(const std::string& key, const std::string& secret);
|
||||
Token(const std::string& key, const std::string& secret, const std::string& pin);
|
||||
|
||||
/** Construct a token, extracting the key and secret from a set of
|
||||
* key-value pairs (e.g. those parsed from an request or access
|
||||
* token request).
|
||||
*/
|
||||
static Token extract(const KeyValuePairs& response);
|
||||
/** Construct a token, extracting the key and secret from a raw,
|
||||
* encoded response.
|
||||
*/
|
||||
static Token extract(const std::string& requestTokenResponse);
|
||||
|
||||
const std::string& key() const { return mKey; }
|
||||
const std::string& secret() const { return mSecret; }
|
||||
|
||||
const std::string& pin() const { return mPin; }
|
||||
void setPin(const std::string& pin_) { mPin = pin_; }
|
||||
|
||||
private:
|
||||
|
||||
const std::string mKey;
|
||||
const std::string mSecret;
|
||||
std::string mPin;
|
||||
};
|
||||
|
||||
class Client {
|
||||
public:
|
||||
/** Perform static initialization. This will be called automatically, but
|
||||
* you can call it explicitly to ensure thread safety. If you do not call
|
||||
* this explicitly before using the Client class, the same nonce may be
|
||||
* generated twice.
|
||||
*/
|
||||
static void initialize();
|
||||
/** Alternative initialize method which lets you specify the seed and
|
||||
* control the timestamp used in generating signatures. This only exists
|
||||
* for testing purposes and should not be used in practice.
|
||||
*/
|
||||
static void initialize(int nonce, time_t timestamp);
|
||||
|
||||
/** Exposed for testing only.
|
||||
*/
|
||||
static void __resetInitialize();
|
||||
|
||||
/** Construct an OAuth Client using only a consumer key and
|
||||
* secret. You can use this to start a three-legged
|
||||
* authentication (to acquire an access token for a user) or for
|
||||
* simple two-legged authentication (signing with empty access
|
||||
* token info).
|
||||
*
|
||||
* \param consumer Consumer information. The caller must ensure
|
||||
* it remains valid during the lifetime of this object
|
||||
*/
|
||||
Client(const Consumer* consumer);
|
||||
/** Construct an OAuth Client with consumer key and secret (yours)
|
||||
* and access token key and secret (acquired and stored during
|
||||
* three-legged authentication).
|
||||
*
|
||||
* \param consumer Consumer information. The caller must ensure
|
||||
* it remains valid during the lifetime of this object
|
||||
* \param token Access token information. The caller must ensure
|
||||
* it remains valid during the lifetime of this object
|
||||
*/
|
||||
Client(const Consumer* consumer, const Token* token);
|
||||
|
||||
~Client();
|
||||
|
||||
/** Build an OAuth HTTP header for the given request. This version provides
|
||||
* only the field value.
|
||||
*
|
||||
* \param eType the HTTP request type, e.g. GET or POST
|
||||
* \param rawUrl the raw request URL (should include query parameters)
|
||||
* \param rawData the raw HTTP request data (can be empty)
|
||||
* \param includeOAuthVerifierPin if true, adds oauth_verifier parameter
|
||||
* \returns a string containing the HTTP header
|
||||
*/
|
||||
std::string getHttpHeader(const Http::RequestType eType,
|
||||
const std::string& rawUrl,
|
||||
const std::string& rawData = "",
|
||||
const bool includeOAuthVerifierPin = false);
|
||||
/** Build an OAuth HTTP header for the given request. This version gives a
|
||||
* fully formatted header, i.e. including the header field name.
|
||||
*
|
||||
* \param eType the HTTP request type, e.g. GET or POST
|
||||
* \param rawUrl the raw request URL (should include query parameters)
|
||||
* \param rawData the raw HTTP request data (can be empty)
|
||||
* \param includeOAuthVerifierPin if true, adds oauth_verifier parameter
|
||||
* \returns a string containing the HTTP header
|
||||
*/
|
||||
std::string getFormattedHttpHeader(const Http::RequestType eType,
|
||||
const std::string& rawUrl,
|
||||
const std::string& rawData = "",
|
||||
const bool includeOAuthVerifierPin = false);
|
||||
/** Build an OAuth HTTP header for the given request.
|
||||
*
|
||||
* \param eType the HTTP request type, e.g. GET or POST
|
||||
* \param rawUrl the raw request URL (should include query parameters)
|
||||
* \param rawData the raw HTTP request data (can be empty)
|
||||
* \param includeOAuthVerifierPin if true, adds oauth_verifier parameter
|
||||
* \returns a string containing the query string, including the query
|
||||
* parameters in the rawUrl
|
||||
*/
|
||||
std::string getURLQueryString(const Http::RequestType eType,
|
||||
const std::string& rawUrl,
|
||||
const std::string& rawData = "",
|
||||
const bool includeOAuthVerifierPin = false);
|
||||
private:
|
||||
/** Disable default constructur -- must provide consumer
|
||||
* information.
|
||||
*/
|
||||
Client();
|
||||
|
||||
static bool initialized;
|
||||
static int testingNonce;
|
||||
static time_t testingTimestamp;
|
||||
|
||||
/* OAuth data */
|
||||
const Consumer* mConsumer;
|
||||
const Token* mToken;
|
||||
std::string m_nonce;
|
||||
std::string m_timeStamp;
|
||||
|
||||
/* OAuth related utility methods */
|
||||
bool buildOAuthTokenKeyValuePairs( const bool includeOAuthVerifierPin, /* in */
|
||||
const std::string& rawData, /* in */
|
||||
const std::string& oauthSignature, /* in */
|
||||
KeyValuePairs& keyValueMap /* out */,
|
||||
const bool urlEncodeValues /* in */,
|
||||
const bool generateTimestamp /* in */);
|
||||
|
||||
bool getStringFromOAuthKeyValuePairs( const KeyValuePairs& rawParamMap, /* in */
|
||||
std::string& rawParams, /* out */
|
||||
const std::string& paramsSeperator /* in */ );
|
||||
|
||||
typedef enum _ParameterStringType {
|
||||
QueryStringString,
|
||||
AuthorizationHeaderString
|
||||
} ParameterStringType;
|
||||
// Utility for building OAuth HTTP header or query string. The string type
|
||||
// controls the separator and also filters parameters: for query strings,
|
||||
// all parameters are included. For HTTP headers, only auth parameters are
|
||||
// included.
|
||||
std::string buildOAuthParameterString(
|
||||
ParameterStringType string_type,
|
||||
const Http::RequestType eType,
|
||||
const std::string& rawUrl,
|
||||
const std::string& rawData,
|
||||
const bool includeOAuthVerifierPin);
|
||||
|
||||
bool getSignature( const Http::RequestType eType, /* in */
|
||||
const std::string& rawUrl, /* in */
|
||||
const KeyValuePairs& rawKeyValuePairs, /* in */
|
||||
std::string& oAuthSignature /* out */ );
|
||||
|
||||
void generateNonceTimeStamp();
|
||||
};
|
||||
|
||||
} // namespace OAuth
|
||||
|
||||
#endif // __LIBOAUTHCPP_LIBOAUTHCPP_H__
|
|
@ -1,59 +0,0 @@
|
|||
//******************************************************************************
|
||||
//* HMAC_SHA1.cpp : Implementation of HMAC SHA1 algorithm
|
||||
//* Comfort to RFC 2104
|
||||
//*
|
||||
//******************************************************************************
|
||||
#include "HMAC_SHA1.h"
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
|
||||
void CHMAC_SHA1::HMAC_SHA1(BYTE *text, int text_len, BYTE *key, int key_len, BYTE *digest)
|
||||
{
|
||||
memset(SHA1_Key, 0, SHA1_BLOCK_SIZE);
|
||||
|
||||
/* repeated 64 times for values in ipad and opad */
|
||||
memset(m_ipad, 0x36, sizeof(m_ipad));
|
||||
memset(m_opad, 0x5c, sizeof(m_opad));
|
||||
|
||||
/* STEP 1 */
|
||||
if (key_len > SHA1_BLOCK_SIZE)
|
||||
{
|
||||
CSHA1::Reset();
|
||||
CSHA1::Update((UINT_8 *)key, key_len);
|
||||
CSHA1::Final();
|
||||
|
||||
CSHA1::GetHash((UINT_8 *)SHA1_Key);
|
||||
}
|
||||
else
|
||||
memcpy(SHA1_Key, key, key_len);
|
||||
|
||||
/* STEP 2 */
|
||||
for (int i=0; i<(int)sizeof(m_ipad); i++)
|
||||
{
|
||||
m_ipad[i] ^= SHA1_Key[i];
|
||||
}
|
||||
|
||||
/* STEP 4 */
|
||||
CSHA1::Reset();
|
||||
CSHA1::Update((UINT_8 *)m_ipad, sizeof(m_ipad));
|
||||
CSHA1::Update((UINT_8 *)text, text_len);
|
||||
CSHA1::Final();
|
||||
|
||||
char szReport[SHA1_DIGEST_LENGTH];
|
||||
CSHA1::GetHash((UINT_8 *)szReport);
|
||||
|
||||
/* STEP 5 */
|
||||
for (int j=0; j<(int)sizeof(m_opad); j++)
|
||||
{
|
||||
m_opad[j] ^= SHA1_Key[j];
|
||||
}
|
||||
|
||||
/*STEP 7 */
|
||||
CSHA1::Reset();
|
||||
CSHA1::Update((UINT_8 *)m_opad, sizeof(m_opad));
|
||||
CSHA1::Update((UINT_8 *)szReport, SHA1_DIGEST_LENGTH);
|
||||
CSHA1::Final();
|
||||
|
||||
CSHA1::GetHash((UINT_8 *)digest);
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
100% free public domain implementation of the HMAC-SHA1 algorithm
|
||||
by Chien-Chung, Chung (Jim Chung) <jimchung1221@gmail.com>
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __HMAC_SHA1_H__
|
||||
#define __HMAC_SHA1_H__
|
||||
|
||||
#include "SHA1.h"
|
||||
|
||||
typedef unsigned char BYTE ;
|
||||
|
||||
class CHMAC_SHA1 : public CSHA1
|
||||
{
|
||||
public:
|
||||
|
||||
enum {
|
||||
SHA1_DIGEST_LENGTH = 20,
|
||||
SHA1_BLOCK_SIZE = 64
|
||||
} ;
|
||||
|
||||
private:
|
||||
BYTE m_ipad[SHA1_BLOCK_SIZE];
|
||||
BYTE m_opad[SHA1_BLOCK_SIZE];
|
||||
|
||||
// This holds one SHA1 block's worth of data, zero padded if necessary.
|
||||
char SHA1_Key[SHA1_BLOCK_SIZE];
|
||||
|
||||
public:
|
||||
CHMAC_SHA1() {}
|
||||
|
||||
void HMAC_SHA1(BYTE *text, int text_len, BYTE *key, int key_len, BYTE *digest);
|
||||
};
|
||||
|
||||
|
||||
#endif /* __HMAC_SHA1_H__ */
|
|
@ -1,277 +0,0 @@
|
|||
/*
|
||||
100% free public domain implementation of the SHA-1 algorithm
|
||||
by Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Web: http://www.dominik-reichl.de/
|
||||
|
||||
Version 1.6 - 2005-02-07 (thanks to Howard Kapustein for patches)
|
||||
- You can set the endianness in your files, no need to modify the
|
||||
header file of the CSHA1 class any more
|
||||
- Aligned data support
|
||||
- Made support/compilation of the utility functions (ReportHash
|
||||
and HashFile) optional (useful, if bytes count, for example in
|
||||
embedded environments)
|
||||
|
||||
Version 1.5 - 2005-01-01
|
||||
- 64-bit compiler compatibility added
|
||||
- Made variable wiping optional (define SHA1_WIPE_VARIABLES)
|
||||
- Removed unnecessary variable initializations
|
||||
- ROL32 improvement for the Microsoft compiler (using _rotl)
|
||||
|
||||
======== Test Vectors (from FIPS PUB 180-1) ========
|
||||
|
||||
SHA1("abc") =
|
||||
A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
|
||||
|
||||
SHA1("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") =
|
||||
84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
|
||||
|
||||
SHA1(A million repetitions of "a") =
|
||||
34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
|
||||
*/
|
||||
|
||||
#include "SHA1.h"
|
||||
#include <cassert>
|
||||
|
||||
#ifdef SHA1_UTILITY_FUNCTIONS
|
||||
#define SHA1_MAX_FILE_BUFFER 8000
|
||||
#endif
|
||||
|
||||
// Rotate x bits to the left
|
||||
#ifndef ROL32
|
||||
#ifdef _MSC_VER
|
||||
#define ROL32(_val32, _nBits) _rotl(_val32, _nBits)
|
||||
#else
|
||||
#define ROL32(_val32, _nBits) (((_val32)<<(_nBits))|((_val32)>>(32-(_nBits))))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef SHA1_LITTLE_ENDIAN
|
||||
#define SHABLK0(i) (m_block->l[i] = \
|
||||
(ROL32(m_block->l[i],24) & 0xFF00FF00) | (ROL32(m_block->l[i],8) & 0x00FF00FF))
|
||||
#else
|
||||
#define SHABLK0(i) (m_block->l[i])
|
||||
#endif
|
||||
|
||||
#define SHABLK(i) (m_block->l[i&15] = ROL32(m_block->l[(i+13)&15] ^ m_block->l[(i+8)&15] \
|
||||
^ m_block->l[(i+2)&15] ^ m_block->l[i&15],1))
|
||||
|
||||
// SHA-1 rounds
|
||||
#define _R0(v,w,x,y,z,i) { z+=((w&(x^y))^y)+SHABLK0(i)+0x5A827999+ROL32(v,5); w=ROL32(w,30); }
|
||||
#define _R1(v,w,x,y,z,i) { z+=((w&(x^y))^y)+SHABLK(i)+0x5A827999+ROL32(v,5); w=ROL32(w,30); }
|
||||
#define _R2(v,w,x,y,z,i) { z+=(w^x^y)+SHABLK(i)+0x6ED9EBA1+ROL32(v,5); w=ROL32(w,30); }
|
||||
#define _R3(v,w,x,y,z,i) { z+=(((w|x)&y)|(w&x))+SHABLK(i)+0x8F1BBCDC+ROL32(v,5); w=ROL32(w,30); }
|
||||
#define _R4(v,w,x,y,z,i) { z+=(w^x^y)+SHABLK(i)+0xCA62C1D6+ROL32(v,5); w=ROL32(w,30); }
|
||||
|
||||
CSHA1::CSHA1()
|
||||
{
|
||||
m_block = (SHA1_WORKSPACE_BLOCK *)m_workspace;
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
CSHA1::~CSHA1()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
void CSHA1::Reset()
|
||||
{
|
||||
// SHA1 initialization constants
|
||||
m_state[0] = 0x67452301;
|
||||
m_state[1] = 0xEFCDAB89;
|
||||
m_state[2] = 0x98BADCFE;
|
||||
m_state[3] = 0x10325476;
|
||||
m_state[4] = 0xC3D2E1F0;
|
||||
|
||||
m_count[0] = 0;
|
||||
m_count[1] = 0;
|
||||
}
|
||||
|
||||
void CSHA1::Transform(UINT_32 *state, UINT_8 *buffer)
|
||||
{
|
||||
// Copy state[] to working vars
|
||||
UINT_32 a = state[0], b = state[1], c = state[2], d = state[3], e = state[4];
|
||||
|
||||
memcpy(m_block, buffer, 64);
|
||||
|
||||
// 4 rounds of 20 operations each. Loop unrolled.
|
||||
_R0(a,b,c,d,e, 0); _R0(e,a,b,c,d, 1); _R0(d,e,a,b,c, 2); _R0(c,d,e,a,b, 3);
|
||||
_R0(b,c,d,e,a, 4); _R0(a,b,c,d,e, 5); _R0(e,a,b,c,d, 6); _R0(d,e,a,b,c, 7);
|
||||
_R0(c,d,e,a,b, 8); _R0(b,c,d,e,a, 9); _R0(a,b,c,d,e,10); _R0(e,a,b,c,d,11);
|
||||
_R0(d,e,a,b,c,12); _R0(c,d,e,a,b,13); _R0(b,c,d,e,a,14); _R0(a,b,c,d,e,15);
|
||||
_R1(e,a,b,c,d,16); _R1(d,e,a,b,c,17); _R1(c,d,e,a,b,18); _R1(b,c,d,e,a,19);
|
||||
_R2(a,b,c,d,e,20); _R2(e,a,b,c,d,21); _R2(d,e,a,b,c,22); _R2(c,d,e,a,b,23);
|
||||
_R2(b,c,d,e,a,24); _R2(a,b,c,d,e,25); _R2(e,a,b,c,d,26); _R2(d,e,a,b,c,27);
|
||||
_R2(c,d,e,a,b,28); _R2(b,c,d,e,a,29); _R2(a,b,c,d,e,30); _R2(e,a,b,c,d,31);
|
||||
_R2(d,e,a,b,c,32); _R2(c,d,e,a,b,33); _R2(b,c,d,e,a,34); _R2(a,b,c,d,e,35);
|
||||
_R2(e,a,b,c,d,36); _R2(d,e,a,b,c,37); _R2(c,d,e,a,b,38); _R2(b,c,d,e,a,39);
|
||||
_R3(a,b,c,d,e,40); _R3(e,a,b,c,d,41); _R3(d,e,a,b,c,42); _R3(c,d,e,a,b,43);
|
||||
_R3(b,c,d,e,a,44); _R3(a,b,c,d,e,45); _R3(e,a,b,c,d,46); _R3(d,e,a,b,c,47);
|
||||
_R3(c,d,e,a,b,48); _R3(b,c,d,e,a,49); _R3(a,b,c,d,e,50); _R3(e,a,b,c,d,51);
|
||||
_R3(d,e,a,b,c,52); _R3(c,d,e,a,b,53); _R3(b,c,d,e,a,54); _R3(a,b,c,d,e,55);
|
||||
_R3(e,a,b,c,d,56); _R3(d,e,a,b,c,57); _R3(c,d,e,a,b,58); _R3(b,c,d,e,a,59);
|
||||
_R4(a,b,c,d,e,60); _R4(e,a,b,c,d,61); _R4(d,e,a,b,c,62); _R4(c,d,e,a,b,63);
|
||||
_R4(b,c,d,e,a,64); _R4(a,b,c,d,e,65); _R4(e,a,b,c,d,66); _R4(d,e,a,b,c,67);
|
||||
_R4(c,d,e,a,b,68); _R4(b,c,d,e,a,69); _R4(a,b,c,d,e,70); _R4(e,a,b,c,d,71);
|
||||
_R4(d,e,a,b,c,72); _R4(c,d,e,a,b,73); _R4(b,c,d,e,a,74); _R4(a,b,c,d,e,75);
|
||||
_R4(e,a,b,c,d,76); _R4(d,e,a,b,c,77); _R4(c,d,e,a,b,78); _R4(b,c,d,e,a,79);
|
||||
|
||||
// Add the working vars back into state
|
||||
state[0] += a;
|
||||
state[1] += b;
|
||||
state[2] += c;
|
||||
state[3] += d;
|
||||
state[4] += e;
|
||||
|
||||
// Wipe variables
|
||||
#ifdef SHA1_WIPE_VARIABLES
|
||||
a = b = c = d = e = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Use this function to hash in binary data and strings
|
||||
void CSHA1::Update(UINT_8 *data, UINT_32 len)
|
||||
{
|
||||
UINT_32 i, j;
|
||||
|
||||
j = (m_count[0] >> 3) & 63;
|
||||
|
||||
if((m_count[0] += len << 3) < (len << 3)) m_count[1]++;
|
||||
|
||||
m_count[1] += (len >> 29);
|
||||
|
||||
if((j + len) > 63)
|
||||
{
|
||||
i = 64 - j;
|
||||
memcpy(&m_buffer[j], data, i);
|
||||
Transform(m_state, m_buffer);
|
||||
|
||||
for(; i + 63 < len; i += 64) Transform(m_state, &data[i]);
|
||||
|
||||
j = 0;
|
||||
}
|
||||
else i = 0;
|
||||
|
||||
memcpy(&m_buffer[j], &data[i], len - i);
|
||||
}
|
||||
|
||||
#ifdef SHA1_UTILITY_FUNCTIONS
|
||||
// Hash in file contents
|
||||
bool CSHA1::HashFile(char *szFileName)
|
||||
{
|
||||
unsigned long ulFileSize, ulRest, ulBlocks;
|
||||
unsigned long i;
|
||||
UINT_8 uData[SHA1_MAX_FILE_BUFFER];
|
||||
FILE *fIn;
|
||||
|
||||
if(szFileName == NULL) return false;
|
||||
|
||||
fIn = fopen(szFileName, "rb");
|
||||
if(fIn == NULL) return false;
|
||||
|
||||
fseek(fIn, 0, SEEK_END);
|
||||
ulFileSize = (unsigned long)ftell(fIn);
|
||||
fseek(fIn, 0, SEEK_SET);
|
||||
|
||||
if(ulFileSize != 0)
|
||||
{
|
||||
ulBlocks = ulFileSize / SHA1_MAX_FILE_BUFFER;
|
||||
ulRest = ulFileSize % SHA1_MAX_FILE_BUFFER;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulBlocks = 0;
|
||||
ulRest = 0;
|
||||
}
|
||||
|
||||
for(i = 0; i < ulBlocks; i++)
|
||||
{
|
||||
size_t nread = fread(uData, 1, SHA1_MAX_FILE_BUFFER, fIn);
|
||||
assert(nread == SHA1_MAX_FILE_BUFFER);
|
||||
Update((UINT_8 *)uData, SHA1_MAX_FILE_BUFFER);
|
||||
}
|
||||
|
||||
if(ulRest != 0)
|
||||
{
|
||||
size_t nread = fread(uData, 1, ulRest, fIn);
|
||||
assert(nread == ulRest);
|
||||
Update((UINT_8 *)uData, ulRest);
|
||||
}
|
||||
|
||||
fclose(fIn); fIn = NULL;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void CSHA1::Final()
|
||||
{
|
||||
UINT_32 i;
|
||||
UINT_8 finalcount[8];
|
||||
|
||||
for(i = 0; i < 8; i++)
|
||||
finalcount[i] = (UINT_8)((m_count[((i >= 4) ? 0 : 1)]
|
||||
>> ((3 - (i & 3)) * 8) ) & 255); // Endian independent
|
||||
|
||||
Update((UINT_8 *)"\200", 1);
|
||||
|
||||
while ((m_count[0] & 504) != 448)
|
||||
Update((UINT_8 *)"\0", 1);
|
||||
|
||||
Update(finalcount, 8); // Cause a SHA1Transform()
|
||||
|
||||
for(i = 0; i < 20; i++)
|
||||
{
|
||||
m_digest[i] = (UINT_8)((m_state[i >> 2] >> ((3 - (i & 3)) * 8) ) & 255);
|
||||
}
|
||||
|
||||
// Wipe variables for security reasons
|
||||
#ifdef SHA1_WIPE_VARIABLES
|
||||
i = 0;
|
||||
memset(m_buffer, 0, 64);
|
||||
memset(m_state, 0, 20);
|
||||
memset(m_count, 0, 8);
|
||||
memset(finalcount, 0, 8);
|
||||
Transform(m_state, m_buffer);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef SHA1_UTILITY_FUNCTIONS
|
||||
// Get the final hash as a pre-formatted string
|
||||
void CSHA1::ReportHash(char *szReport, unsigned char uReportType)
|
||||
{
|
||||
unsigned char i;
|
||||
char szTemp[16];
|
||||
|
||||
if(szReport == NULL) return;
|
||||
|
||||
if(uReportType == REPORT_HEX)
|
||||
{
|
||||
snprintf(szTemp, sizeof(szTemp), "%02X", m_digest[0]);
|
||||
strcat(szReport, szTemp);
|
||||
|
||||
for(i = 1; i < 20; i++)
|
||||
{
|
||||
snprintf(szTemp, sizeof(szTemp), " %02X", m_digest[i]);
|
||||
strcat(szReport, szTemp);
|
||||
}
|
||||
}
|
||||
else if(uReportType == REPORT_DIGIT)
|
||||
{
|
||||
snprintf(szTemp, sizeof(szTemp), "%u", m_digest[0]);
|
||||
strcat(szReport, szTemp);
|
||||
|
||||
for(i = 1; i < 20; i++)
|
||||
{
|
||||
snprintf(szTemp, sizeof(szTemp), " %u", m_digest[i]);
|
||||
strcat(szReport, szTemp);
|
||||
}
|
||||
}
|
||||
else strcpy(szReport, "Error: Unknown report type!");
|
||||
}
|
||||
#endif
|
||||
|
||||
// Get the raw message digest
|
||||
void CSHA1::GetHash(UINT_8 *puDest)
|
||||
{
|
||||
memcpy(puDest, m_digest, 20);
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
/*
|
||||
100% free public domain implementation of the SHA-1 algorithm
|
||||
by Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Web: http://www.dominik-reichl.de/
|
||||
|
||||
Version 1.6 - 2005-02-07 (thanks to Howard Kapustein for patches)
|
||||
- You can set the endianness in your files, no need to modify the
|
||||
header file of the CSHA1 class any more
|
||||
- Aligned data support
|
||||
- Made support/compilation of the utility functions (ReportHash
|
||||
and HashFile) optional (useful, if bytes count, for example in
|
||||
embedded environments)
|
||||
|
||||
Version 1.5 - 2005-01-01
|
||||
- 64-bit compiler compatibility added
|
||||
- Made variable wiping optional (define SHA1_WIPE_VARIABLES)
|
||||
- Removed unnecessary variable initializations
|
||||
- ROL32 improvement for the Microsoft compiler (using _rotl)
|
||||
|
||||
======== Test Vectors (from FIPS PUB 180-1) ========
|
||||
|
||||
SHA1("abc") =
|
||||
A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
|
||||
|
||||
SHA1("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") =
|
||||
84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
|
||||
|
||||
SHA1(A million repetitions of "a") =
|
||||
34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
|
||||
*/
|
||||
|
||||
#ifndef ___SHA1_HDR___
|
||||
#define ___SHA1_HDR___
|
||||
|
||||
#if !defined(SHA1_UTILITY_FUNCTIONS) && !defined(SHA1_NO_UTILITY_FUNCTIONS)
|
||||
#define SHA1_UTILITY_FUNCTIONS
|
||||
#endif
|
||||
|
||||
#include <memory.h> // Needed for memset and memcpy
|
||||
|
||||
#ifdef SHA1_UTILITY_FUNCTIONS
|
||||
#include <stdio.h> // Needed for file access and sprintf
|
||||
#include <string.h> // Needed for strcat and strcpy
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
// You can define the endian mode in your files, without modifying the SHA1
|
||||
// source files. Just #define SHA1_LITTLE_ENDIAN or #define SHA1_BIG_ENDIAN
|
||||
// in your files, before including the SHA1.h header file. If you don't
|
||||
// define anything, the class defaults to little endian.
|
||||
|
||||
#if !defined(SHA1_LITTLE_ENDIAN) && !defined(SHA1_BIG_ENDIAN)
|
||||
#define SHA1_LITTLE_ENDIAN
|
||||
#endif
|
||||
|
||||
// Same here. If you want variable wiping, #define SHA1_WIPE_VARIABLES, if
|
||||
// not, #define SHA1_NO_WIPE_VARIABLES. If you don't define anything, it
|
||||
// defaults to wiping.
|
||||
|
||||
#if !defined(SHA1_WIPE_VARIABLES) && !defined(SHA1_NO_WIPE_VARIABLES)
|
||||
#define SHA1_WIPE_VARIABLES
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Define 8- and 32-bit variables
|
||||
|
||||
#ifndef UINT_32
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#define UINT_8 unsigned __int8
|
||||
#define UINT_32 unsigned __int32
|
||||
|
||||
#else
|
||||
|
||||
#define UINT_8 unsigned char
|
||||
|
||||
#if (ULONG_MAX == 0xFFFFFFFF && UINT_MAX < ULONG_MAX)
|
||||
#define UINT_32 unsigned long
|
||||
#else
|
||||
#define UINT_32 unsigned int
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Declare SHA1 workspace
|
||||
|
||||
typedef union
|
||||
{
|
||||
UINT_8 c[64];
|
||||
UINT_32 l[16];
|
||||
} SHA1_WORKSPACE_BLOCK;
|
||||
|
||||
class CSHA1
|
||||
{
|
||||
public:
|
||||
#ifdef SHA1_UTILITY_FUNCTIONS
|
||||
// Two different formats for ReportHash(...)
|
||||
enum
|
||||
{
|
||||
REPORT_HEX = 0,
|
||||
REPORT_DIGIT = 1
|
||||
};
|
||||
#endif
|
||||
|
||||
// Constructor and Destructor
|
||||
CSHA1();
|
||||
~CSHA1();
|
||||
|
||||
UINT_32 m_state[5];
|
||||
UINT_32 m_count[2];
|
||||
UINT_32 __reserved1[1];
|
||||
UINT_8 m_buffer[64];
|
||||
UINT_8 m_digest[20];
|
||||
UINT_32 __reserved2[3];
|
||||
|
||||
void Reset();
|
||||
|
||||
// Update the hash value
|
||||
void Update(UINT_8 *data, UINT_32 len);
|
||||
#ifdef SHA1_UTILITY_FUNCTIONS
|
||||
bool HashFile(char *szFileName);
|
||||
#endif
|
||||
|
||||
// Finalize hash and report
|
||||
void Final();
|
||||
|
||||
// Report functions: as pre-formatted and raw data
|
||||
#ifdef SHA1_UTILITY_FUNCTIONS
|
||||
void ReportHash(char *szReport, unsigned char uReportType = REPORT_HEX);
|
||||
#endif
|
||||
void GetHash(UINT_8 *puDest);
|
||||
|
||||
private:
|
||||
// Private SHA-1 transformation
|
||||
void Transform(UINT_32 *state, UINT_8 *buffer);
|
||||
|
||||
// Member variables
|
||||
UINT_8 m_workspace[64];
|
||||
SHA1_WORKSPACE_BLOCK *m_block; // SHA1 pointer to the byte array above
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,123 +0,0 @@
|
|||
/*
|
||||
base64.cpp and base64.h
|
||||
|
||||
Copyright (C) 2004-2008 René Nyffenegger
|
||||
|
||||
This source code is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the author be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this source code must not be misrepresented; you must not
|
||||
claim that you wrote the original source code. If you use this source code
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original source code.
|
||||
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
|
||||
|
||||
*/
|
||||
|
||||
#include "base64.h"
|
||||
#include <iostream>
|
||||
|
||||
static const std::string base64_chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
|
||||
|
||||
static inline bool is_base64(unsigned char c) {
|
||||
return (isalnum(c) || (c == '+') || (c == '/'));
|
||||
}
|
||||
|
||||
std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) {
|
||||
std::string ret;
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
unsigned char char_array_3[3];
|
||||
unsigned char char_array_4[4];
|
||||
|
||||
while (in_len--) {
|
||||
char_array_3[i++] = *(bytes_to_encode++);
|
||||
if (i == 3) {
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
|
||||
for(i = 0; (i <4) ; i++)
|
||||
ret += base64_chars[char_array_4[i]];
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i)
|
||||
{
|
||||
for(j = i; j < 3; j++)
|
||||
char_array_3[j] = '\0';
|
||||
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
|
||||
for (j = 0; (j < i + 1); j++)
|
||||
ret += base64_chars[char_array_4[j]];
|
||||
|
||||
while((i++ < 3))
|
||||
ret += '=';
|
||||
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
std::string base64_decode(std::string const& encoded_string) {
|
||||
int in_len = encoded_string.size();
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
int in_ = 0;
|
||||
unsigned char char_array_4[4], char_array_3[3];
|
||||
std::string ret;
|
||||
|
||||
while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
|
||||
char_array_4[i++] = encoded_string[in_]; in_++;
|
||||
if (i ==4) {
|
||||
for (i = 0; i <4; i++)
|
||||
char_array_4[i] = base64_chars.find(char_array_4[i]);
|
||||
|
||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for (i = 0; (i < 3); i++)
|
||||
ret += char_array_3[i];
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i) {
|
||||
for (j = i; j <4; j++)
|
||||
char_array_4[j] = 0;
|
||||
|
||||
for (j = 0; j <4; j++)
|
||||
char_array_4[j] = base64_chars.find(char_array_4[j]);
|
||||
|
||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
#include <string>
|
||||
|
||||
std::string base64_encode(unsigned char const* , unsigned int len);
|
||||
std::string base64_decode(std::string const& s);
|
|
@ -1,621 +0,0 @@
|
|||
#include <liboauthcpp/liboauthcpp.h>
|
||||
#include "HMAC_SHA1.h"
|
||||
#include "base64.h"
|
||||
#include "urlencode.h"
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
namespace OAuth {
|
||||
|
||||
namespace Defaults
|
||||
{
|
||||
/* Constants */
|
||||
const int BUFFSIZE = 1024;
|
||||
const int BUFFSIZE_LARGE = 1024;
|
||||
const std::string CONSUMERKEY_KEY = "oauth_consumer_key";
|
||||
const std::string CALLBACK_KEY = "oauth_callback";
|
||||
const std::string VERSION_KEY = "oauth_version";
|
||||
const std::string SIGNATUREMETHOD_KEY = "oauth_signature_method";
|
||||
const std::string SIGNATURE_KEY = "oauth_signature";
|
||||
const std::string TIMESTAMP_KEY = "oauth_timestamp";
|
||||
const std::string NONCE_KEY = "oauth_nonce";
|
||||
const std::string TOKEN_KEY = "oauth_token";
|
||||
const std::string TOKENSECRET_KEY = "oauth_token_secret";
|
||||
const std::string VERIFIER_KEY = "oauth_verifier";
|
||||
|
||||
const std::string AUTHHEADER_FIELD = "Authorization: ";
|
||||
const std::string AUTHHEADER_PREFIX = "OAuth ";
|
||||
};
|
||||
|
||||
/** std::string -> std::string conversion function */
|
||||
typedef std::string(*StringConvertFunction)(const std::string&);
|
||||
|
||||
LogLevel gLogLevel = LogLevelNone;
|
||||
|
||||
void SetLogLevel(LogLevel lvl) {
|
||||
gLogLevel = lvl;
|
||||
}
|
||||
#define LOG(lvl, msg) \
|
||||
do { \
|
||||
if (lvl <= gLogLevel) std::cerr << "OAUTH: " << msg << std::endl; \
|
||||
} while(0)
|
||||
|
||||
std::string PercentEncode(const std::string& decoded) {
|
||||
return urlencode(decoded, URLEncode_Everything);
|
||||
}
|
||||
|
||||
std::string URLEncode(const std::string& decoded) {
|
||||
return PercentEncode(decoded);
|
||||
}
|
||||
|
||||
std::string HttpEncodePath(const std::string& decoded) {
|
||||
return urlencode(decoded, URLEncode_Path);
|
||||
}
|
||||
|
||||
std::string HttpEncodeQueryKey(const std::string& decoded) {
|
||||
return urlencode(decoded, URLEncode_QueryKey);
|
||||
}
|
||||
|
||||
std::string HttpEncodeQueryValue(const std::string& decoded) {
|
||||
return urlencode(decoded, URLEncode_QueryValue);
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::string PassThrough(const std::string& decoded) {
|
||||
return decoded;
|
||||
}
|
||||
|
||||
std::string RequestTypeString(const Http::RequestType rt) {
|
||||
switch(rt) {
|
||||
case Http::Invalid: return "Invalid Request Type"; break;
|
||||
case Http::Head: return "HEAD"; break;
|
||||
case Http::Get: return "GET"; break;
|
||||
case Http::Post: return "POST"; break;
|
||||
case Http::Delete: return "DELETE"; break;
|
||||
case Http::Put: return "PUT"; break;
|
||||
default: return "Unknown Request Type"; break;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Parse a single key-value pair
|
||||
static std::pair<std::string, std::string> ParseKeyValuePair(const std::string& encoded) {
|
||||
std::size_t eq_pos = encoded.find("=");
|
||||
if (eq_pos == std::string::npos)
|
||||
throw ParseError("Failed to find '=' in key-value pair.");
|
||||
return std::pair<std::string, std::string>(
|
||||
encoded.substr(0, eq_pos),
|
||||
encoded.substr(eq_pos+1)
|
||||
);
|
||||
}
|
||||
|
||||
KeyValuePairs ParseKeyValuePairs(const std::string& encoded) {
|
||||
KeyValuePairs result;
|
||||
|
||||
if (encoded.length() == 0) return result;
|
||||
|
||||
// Split by &
|
||||
std::size_t last_amp = 0;
|
||||
// We can bail when the last one "found" was the end of the string
|
||||
while(true) {
|
||||
std::size_t next_amp = encoded.find('&', last_amp+1);
|
||||
std::string keyval =
|
||||
(next_amp == std::string::npos) ?
|
||||
encoded.substr(last_amp) :
|
||||
encoded.substr(last_amp, next_amp-last_amp);
|
||||
result.insert(ParseKeyValuePair(keyval));
|
||||
// Track spot after the & so the first iteration works without dealing
|
||||
// with -1 index
|
||||
last_amp = next_amp+1;
|
||||
|
||||
// Exit condition
|
||||
if (next_amp == std::string::npos) break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Helper for parameters in key-value pair lists that should only appear
|
||||
// once. Either replaces an existing entry or adds a new entry.
|
||||
static void ReplaceOrInsertKeyValuePair(KeyValuePairs& kvp, const std::string& key, const std::string& value) {
|
||||
assert(kvp.count(key) <= 1);
|
||||
KeyValuePairs::iterator it = kvp.find(key);
|
||||
if (it != kvp.end())
|
||||
it->second = value;
|
||||
else
|
||||
kvp.insert(KeyValuePairs::value_type(key, value));
|
||||
}
|
||||
|
||||
Consumer::Consumer(const std::string& key, const std::string& secret)
|
||||
: mKey(key), mSecret(secret)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
Token::Token(const std::string& key, const std::string& secret)
|
||||
: mKey(key), mSecret(secret)
|
||||
{
|
||||
}
|
||||
|
||||
Token::Token(const std::string& key, const std::string& secret, const std::string& pin)
|
||||
: mKey(key), mSecret(secret), mPin(pin)
|
||||
{
|
||||
}
|
||||
|
||||
Token Token::extract(const std::string& response) {
|
||||
return Token::extract(ParseKeyValuePairs(response));
|
||||
}
|
||||
|
||||
Token Token::extract(const KeyValuePairs& response) {
|
||||
std::string token_key, token_secret;
|
||||
|
||||
KeyValuePairs::const_iterator it = response.find(Defaults::TOKEN_KEY);
|
||||
if (it == response.end())
|
||||
throw MissingKeyError("Couldn't find oauth_token in response");
|
||||
token_key = it->second;
|
||||
|
||||
it = response.find(Defaults::TOKENSECRET_KEY);
|
||||
if (it == response.end())
|
||||
throw MissingKeyError("Couldn't find oauth_token_secret in response");
|
||||
token_secret = it->second;
|
||||
|
||||
return Token(token_key, token_secret);
|
||||
}
|
||||
|
||||
|
||||
bool Client::initialized = false;
|
||||
int Client::testingNonce = 0;
|
||||
time_t Client::testingTimestamp = 0;
|
||||
|
||||
void Client::initialize() {
|
||||
if(!initialized) {
|
||||
srand( time( NULL ) );
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Client::initialize(int nonce, time_t timestamp) {
|
||||
if(!initialized) {
|
||||
testingNonce = nonce;
|
||||
testingTimestamp = timestamp;
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Client::__resetInitialize() {
|
||||
testingNonce = 0;
|
||||
testingTimestamp = 0;
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
Client::Client(const Consumer* consumer)
|
||||
: mConsumer(consumer),
|
||||
mToken(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
Client::Client(const Consumer* consumer, const Token* token)
|
||||
: mConsumer(consumer),
|
||||
mToken(token)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Client::~Client()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*++
|
||||
* @method: Client::generateNonceTimeStamp
|
||||
*
|
||||
* @description: this method generates nonce and timestamp for OAuth header
|
||||
*
|
||||
* @input: none
|
||||
*
|
||||
* @output: none
|
||||
*
|
||||
* @remarks: internal method
|
||||
*
|
||||
*--*/
|
||||
void Client::generateNonceTimeStamp()
|
||||
{
|
||||
// Make sure the random seed has been initialized
|
||||
Client::initialize();
|
||||
|
||||
char szTime[Defaults::BUFFSIZE];
|
||||
char szRand[Defaults::BUFFSIZE];
|
||||
memset( szTime, 0, Defaults::BUFFSIZE );
|
||||
memset( szRand, 0, Defaults::BUFFSIZE );
|
||||
|
||||
// Any non-zero timestamp triggers testing mode with fixed values. Fixing
|
||||
// both values makes life easier because generating a signature is
|
||||
// idempotent -- otherwise using macros can cause double evaluation and
|
||||
// incorrect results because of repeated calls to rand().
|
||||
snprintf( szRand, sizeof(szRand), "%x", ((testingTimestamp != 0) ? testingNonce : rand()) );
|
||||
snprintf( szTime, sizeof(szTime), "%ld", ((testingTimestamp != 0) ? testingTimestamp : time( NULL )) );
|
||||
|
||||
m_nonce.assign( szTime );
|
||||
m_nonce.append( szRand );
|
||||
|
||||
m_timeStamp.assign( szTime );
|
||||
}
|
||||
|
||||
/*++
|
||||
* @method: Client::buildOAuthTokenKeyValuePairs
|
||||
*
|
||||
* @description: this method prepares key-value pairs required for OAuth header
|
||||
* and signature generation.
|
||||
*
|
||||
* @input: includeOAuthVerifierPin - flag to indicate whether oauth_verifer key-value
|
||||
* pair needs to be included. oauth_verifer is only
|
||||
* used during exchanging request token with access token.
|
||||
* rawData - url encoded data. this is used during signature generation.
|
||||
* oauthSignature - base64 and url encoded OAuth signature.
|
||||
* generateTimestamp - If true, then generate new timestamp for nonce.
|
||||
*
|
||||
* @input: urlEncodeValues - if true, URLEncode the values inserted into the
|
||||
* output keyValueMap
|
||||
* @output: keyValueMap - map in which key-value pairs are populated
|
||||
*
|
||||
* @remarks: internal method
|
||||
*
|
||||
*--*/
|
||||
bool Client::buildOAuthTokenKeyValuePairs( const bool includeOAuthVerifierPin,
|
||||
const std::string& rawData,
|
||||
const std::string& oauthSignature,
|
||||
KeyValuePairs& keyValueMap,
|
||||
const bool urlEncodeValues,
|
||||
const bool generateTimestamp )
|
||||
{
|
||||
// Encodes value part of key-value pairs depending on type of output (query
|
||||
// string vs. HTTP headers.
|
||||
StringConvertFunction value_encoder = (urlEncodeValues ? HttpEncodeQueryValue : PassThrough);
|
||||
|
||||
/* Generate nonce and timestamp if required */
|
||||
if( generateTimestamp )
|
||||
{
|
||||
generateNonceTimeStamp();
|
||||
}
|
||||
|
||||
/* Consumer key and its value */
|
||||
ReplaceOrInsertKeyValuePair(keyValueMap, Defaults::CONSUMERKEY_KEY, value_encoder(mConsumer->key()));
|
||||
|
||||
/* Nonce key and its value */
|
||||
ReplaceOrInsertKeyValuePair(keyValueMap, Defaults::NONCE_KEY, value_encoder(m_nonce));
|
||||
|
||||
/* Signature if supplied */
|
||||
if( oauthSignature.length() )
|
||||
{
|
||||
// Signature is exempt from encoding. The procedure for
|
||||
// computing it already percent-encodes it as required by the
|
||||
// spec for both query string and Auth header
|
||||
// methods. Therefore, it's pass-through in both cases.
|
||||
ReplaceOrInsertKeyValuePair(keyValueMap, Defaults::SIGNATURE_KEY, oauthSignature);
|
||||
}
|
||||
|
||||
/* Signature method, only HMAC-SHA1 as of now */
|
||||
ReplaceOrInsertKeyValuePair(keyValueMap, Defaults::SIGNATUREMETHOD_KEY, std::string( "HMAC-SHA1" ));
|
||||
|
||||
/* Timestamp */
|
||||
ReplaceOrInsertKeyValuePair(keyValueMap, Defaults::TIMESTAMP_KEY, value_encoder(m_timeStamp));
|
||||
|
||||
/* Token */
|
||||
if( mToken && mToken->key().length() )
|
||||
{
|
||||
ReplaceOrInsertKeyValuePair(keyValueMap, Defaults::TOKEN_KEY, value_encoder(mToken->key()));
|
||||
}
|
||||
|
||||
/* Verifier */
|
||||
if( includeOAuthVerifierPin && mToken && mToken->pin().length() )
|
||||
{
|
||||
ReplaceOrInsertKeyValuePair(keyValueMap, Defaults::VERIFIER_KEY, value_encoder(mToken->pin()));
|
||||
}
|
||||
|
||||
/* Version */
|
||||
ReplaceOrInsertKeyValuePair(keyValueMap, Defaults::VERSION_KEY, std::string( "1.0" ));
|
||||
|
||||
/* Data if it's present */
|
||||
if( rawData.length() )
|
||||
{
|
||||
/* Data should already be urlencoded once */
|
||||
std::string dummyStrKey;
|
||||
std::string dummyStrValue;
|
||||
size_t nPos = rawData.find_first_of( "=" );
|
||||
if( std::string::npos != nPos )
|
||||
{
|
||||
dummyStrKey = rawData.substr( 0, nPos );
|
||||
dummyStrValue = rawData.substr( nPos + 1 );
|
||||
ReplaceOrInsertKeyValuePair(keyValueMap, dummyStrKey, dummyStrValue);
|
||||
}
|
||||
}
|
||||
|
||||
return ( keyValueMap.size() ) ? true : false;
|
||||
}
|
||||
|
||||
/*++
|
||||
* @method: Client::getSignature
|
||||
*
|
||||
* @description: this method calculates HMAC-SHA1 signature of OAuth header
|
||||
*
|
||||
* @input: eType - HTTP request type
|
||||
* rawUrl - raw url of the HTTP request
|
||||
* rawKeyValuePairs - key-value pairs containing OAuth headers and HTTP data
|
||||
*
|
||||
* @output: oAuthSignature - base64 and url encoded signature
|
||||
*
|
||||
* @remarks: internal method
|
||||
*
|
||||
*--*/
|
||||
bool Client::getSignature( const Http::RequestType eType,
|
||||
const std::string& rawUrl,
|
||||
const KeyValuePairs& rawKeyValuePairs,
|
||||
std::string& oAuthSignature )
|
||||
{
|
||||
std::string rawParams;
|
||||
std::string paramsSeperator;
|
||||
std::string sigBase;
|
||||
|
||||
/* Initially empty signature */
|
||||
oAuthSignature.assign( "" );
|
||||
|
||||
/* Build a string using key-value pairs */
|
||||
paramsSeperator = "&";
|
||||
getStringFromOAuthKeyValuePairs( rawKeyValuePairs, rawParams, paramsSeperator );
|
||||
LOG(LogLevelDebug, "Normalized parameters: " << rawParams);
|
||||
|
||||
/* Start constructing base signature string. Refer http://dev.twitter.com/auth#intro */
|
||||
switch( eType )
|
||||
{
|
||||
case Http::Head:
|
||||
{
|
||||
sigBase.assign( "HEAD&" );
|
||||
}
|
||||
break;
|
||||
|
||||
case Http::Get:
|
||||
{
|
||||
sigBase.assign( "GET&" );
|
||||
}
|
||||
break;
|
||||
|
||||
case Http::Post:
|
||||
{
|
||||
sigBase.assign( "POST&" );
|
||||
}
|
||||
break;
|
||||
|
||||
case Http::Delete:
|
||||
{
|
||||
sigBase.assign( "DELETE&" );
|
||||
}
|
||||
break;
|
||||
|
||||
case Http::Put:
|
||||
{
|
||||
sigBase.assign( "PUT&" );
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
sigBase.append( PercentEncode( rawUrl ) );
|
||||
sigBase.append( "&" );
|
||||
sigBase.append( PercentEncode( rawParams ) );
|
||||
LOG(LogLevelDebug, "Signature base string: " << sigBase);
|
||||
|
||||
/* Now, hash the signature base string using HMAC_SHA1 class */
|
||||
CHMAC_SHA1 objHMACSHA1;
|
||||
std::string secretSigningKey;
|
||||
unsigned char strDigest[Defaults::BUFFSIZE_LARGE];
|
||||
|
||||
memset( strDigest, 0, Defaults::BUFFSIZE_LARGE );
|
||||
|
||||
/* Signing key is composed of consumer_secret&token_secret */
|
||||
secretSigningKey.assign( PercentEncode(mConsumer->secret()) );
|
||||
secretSigningKey.append( "&" );
|
||||
if( mToken && mToken->secret().length() )
|
||||
{
|
||||
secretSigningKey.append( PercentEncode(mToken->secret()) );
|
||||
}
|
||||
|
||||
objHMACSHA1.HMAC_SHA1( (unsigned char*)sigBase.c_str(),
|
||||
sigBase.length(),
|
||||
(unsigned char*)secretSigningKey.c_str(),
|
||||
secretSigningKey.length(),
|
||||
strDigest );
|
||||
|
||||
/* Do a base64 encode of signature */
|
||||
std::string base64Str = base64_encode( strDigest, 20 /* SHA 1 digest is 160 bits */ );
|
||||
LOG(LogLevelDebug, "Signature: " << base64Str);
|
||||
|
||||
/* Do an url encode */
|
||||
oAuthSignature = PercentEncode( base64Str );
|
||||
LOG(LogLevelDebug, "Percent-encoded Signature: " << oAuthSignature);
|
||||
|
||||
return ( oAuthSignature.length() ) ? true : false;
|
||||
}
|
||||
|
||||
std::string Client::getHttpHeader(const Http::RequestType eType,
|
||||
const std::string& rawUrl,
|
||||
const std::string& rawData,
|
||||
const bool includeOAuthVerifierPin)
|
||||
{
|
||||
return Defaults::AUTHHEADER_PREFIX + buildOAuthParameterString(AuthorizationHeaderString, eType, rawUrl, rawData, includeOAuthVerifierPin);
|
||||
}
|
||||
|
||||
std::string Client::getFormattedHttpHeader(const Http::RequestType eType,
|
||||
const std::string& rawUrl,
|
||||
const std::string& rawData,
|
||||
const bool includeOAuthVerifierPin)
|
||||
{
|
||||
return Defaults::AUTHHEADER_FIELD + Defaults::AUTHHEADER_PREFIX + buildOAuthParameterString(AuthorizationHeaderString, eType, rawUrl, rawData, includeOAuthVerifierPin);
|
||||
}
|
||||
|
||||
std::string Client::getURLQueryString(const Http::RequestType eType,
|
||||
const std::string& rawUrl,
|
||||
const std::string& rawData,
|
||||
const bool includeOAuthVerifierPin)
|
||||
{
|
||||
return buildOAuthParameterString(QueryStringString, eType, rawUrl, rawData, includeOAuthVerifierPin);
|
||||
}
|
||||
|
||||
std::string Client::buildOAuthParameterString(
|
||||
ParameterStringType string_type,
|
||||
const Http::RequestType eType,
|
||||
const std::string& rawUrl,
|
||||
const std::string& rawData,
|
||||
const bool includeOAuthVerifierPin)
|
||||
{
|
||||
KeyValuePairs rawKeyValuePairs;
|
||||
std::string rawParams;
|
||||
std::string oauthSignature;
|
||||
std::string paramsSeperator;
|
||||
std::string pureUrl( rawUrl );
|
||||
|
||||
LOG(LogLevelDebug, "Signing request " << RequestTypeString(eType) << " " << rawUrl << " " << rawData);
|
||||
|
||||
std::string separator;
|
||||
bool do_urlencode;
|
||||
if (string_type == AuthorizationHeaderString) {
|
||||
separator = ",";
|
||||
do_urlencode = false;
|
||||
}
|
||||
else { // QueryStringString
|
||||
separator = "&";
|
||||
do_urlencode = true;
|
||||
}
|
||||
|
||||
/* Clear header string initially */
|
||||
rawKeyValuePairs.clear();
|
||||
|
||||
/* If URL itself contains ?key=value, then extract and put them in map */
|
||||
size_t nPos = rawUrl.find_first_of( "?" );
|
||||
if( std::string::npos != nPos )
|
||||
{
|
||||
/* Get only URL */
|
||||
pureUrl = rawUrl.substr( 0, nPos );
|
||||
|
||||
/* Get only key=value data part */
|
||||
std::string dataPart = rawUrl.substr( nPos + 1 );
|
||||
rawKeyValuePairs = ParseKeyValuePairs(dataPart);
|
||||
}
|
||||
|
||||
// NOTE: We always request URL encoding on the first pass so that the
|
||||
// signature generation works properly. This *relies* on
|
||||
// buildOAuthTokenKeyValuePairs overwriting values when we do the second
|
||||
// pass to get the values in the form we actually want. The signature and
|
||||
// rawdata are the only things that change, but the signature is only used
|
||||
// in the second pass and the rawdata is already encoded, regardless of
|
||||
// request type.
|
||||
|
||||
/* Build key-value pairs needed for OAuth request token, without signature */
|
||||
buildOAuthTokenKeyValuePairs( includeOAuthVerifierPin, rawData, std::string( "" ), rawKeyValuePairs, true, true );
|
||||
|
||||
/* Get url encoded base64 signature using request type, url and parameters */
|
||||
getSignature( eType, pureUrl, rawKeyValuePairs, oauthSignature );
|
||||
|
||||
/* Now, again build key-value pairs with signature this time */
|
||||
buildOAuthTokenKeyValuePairs( includeOAuthVerifierPin, std::string( "" ), oauthSignature, rawKeyValuePairs, do_urlencode, false );
|
||||
|
||||
/* Get OAuth header in string format. If we're getting the Authorization
|
||||
* header, we need to filter out other parameters.
|
||||
*/
|
||||
if (string_type == AuthorizationHeaderString) {
|
||||
KeyValuePairs oauthKeyValuePairs;
|
||||
std::vector<std::string> oauth_keys;
|
||||
oauth_keys.push_back(Defaults::CONSUMERKEY_KEY);
|
||||
oauth_keys.push_back(Defaults::NONCE_KEY);
|
||||
oauth_keys.push_back(Defaults::SIGNATURE_KEY);
|
||||
oauth_keys.push_back(Defaults::SIGNATUREMETHOD_KEY);
|
||||
oauth_keys.push_back(Defaults::TIMESTAMP_KEY);
|
||||
oauth_keys.push_back(Defaults::TOKEN_KEY);
|
||||
oauth_keys.push_back(Defaults::VERIFIER_KEY);
|
||||
oauth_keys.push_back(Defaults::VERSION_KEY);
|
||||
|
||||
for(size_t i = 0; i < oauth_keys.size(); i++) {
|
||||
assert(rawKeyValuePairs.count(oauth_keys[i]) <= 1);
|
||||
KeyValuePairs::iterator oauth_key_it = rawKeyValuePairs.find(oauth_keys[i]);
|
||||
if (oauth_key_it != rawKeyValuePairs.end())
|
||||
ReplaceOrInsertKeyValuePair(oauthKeyValuePairs, oauth_keys[i], oauth_key_it->second);
|
||||
}
|
||||
getStringFromOAuthKeyValuePairs( oauthKeyValuePairs, rawParams, separator );
|
||||
}
|
||||
else if (string_type == QueryStringString) {
|
||||
getStringFromOAuthKeyValuePairs( rawKeyValuePairs, rawParams, separator );
|
||||
}
|
||||
|
||||
/* Build authorization header */
|
||||
return rawParams;
|
||||
}
|
||||
|
||||
/*++
|
||||
* @method: Client::getStringFromOAuthKeyValuePairs
|
||||
*
|
||||
* @description: this method builds a sorted string from key-value pairs
|
||||
*
|
||||
* @input: rawParamMap - key-value pairs map
|
||||
* paramsSeperator - sepearator, either & or ,
|
||||
*
|
||||
* @output: rawParams - sorted string of OAuth parameters
|
||||
*
|
||||
* @remarks: internal method
|
||||
*
|
||||
*--*/
|
||||
bool Client::getStringFromOAuthKeyValuePairs( const KeyValuePairs& rawParamMap,
|
||||
std::string& rawParams,
|
||||
const std::string& paramsSeperator )
|
||||
{
|
||||
rawParams.assign( "" );
|
||||
if( rawParamMap.size() )
|
||||
{
|
||||
KeyValueList keyValueList;
|
||||
std::string dummyStr;
|
||||
|
||||
/* Push key-value pairs to a list of strings */
|
||||
keyValueList.clear();
|
||||
KeyValuePairs::const_iterator itMap = rawParamMap.begin();
|
||||
for( ; itMap != rawParamMap.end(); itMap++ )
|
||||
{
|
||||
dummyStr.assign( itMap->first );
|
||||
dummyStr.append( "=" );
|
||||
if( paramsSeperator == "," )
|
||||
{
|
||||
dummyStr.append( "\"" );
|
||||
}
|
||||
dummyStr.append( itMap->second );
|
||||
if( paramsSeperator == "," )
|
||||
{
|
||||
dummyStr.append( "\"" );
|
||||
}
|
||||
keyValueList.push_back( dummyStr );
|
||||
}
|
||||
|
||||
/* Sort key-value pairs based on key name */
|
||||
keyValueList.sort();
|
||||
|
||||
/* Now, form a string */
|
||||
dummyStr.assign( "" );
|
||||
KeyValueList::iterator itKeyValue = keyValueList.begin();
|
||||
for( ; itKeyValue != keyValueList.end(); itKeyValue++ )
|
||||
{
|
||||
if( dummyStr.length() )
|
||||
{
|
||||
dummyStr.append( paramsSeperator );
|
||||
}
|
||||
dummyStr.append( itKeyValue->c_str() );
|
||||
}
|
||||
rawParams.assign( dummyStr );
|
||||
}
|
||||
return ( rawParams.length() ) ? true : false;
|
||||
}
|
||||
|
||||
|
||||
} // namespace OAuth
|
|
@ -1,102 +0,0 @@
|
|||
#include "urlencode.h"
|
||||
#include <cassert>
|
||||
|
||||
std::string char2hex( char dec )
|
||||
{
|
||||
char dig1 = (dec&0xF0)>>4;
|
||||
char dig2 = (dec&0x0F);
|
||||
if ( 0<= dig1 && dig1<= 9) dig1+=48; //0,48 in ascii
|
||||
if (10<= dig1 && dig1<=15) dig1+=65-10; //A,65 in ascii
|
||||
if ( 0<= dig2 && dig2<= 9) dig2+=48;
|
||||
if (10<= dig2 && dig2<=15) dig2+=65-10;
|
||||
|
||||
std::string r;
|
||||
r.append( &dig1, 1);
|
||||
r.append( &dig2, 1);
|
||||
return r;
|
||||
}
|
||||
|
||||
std::string urlencode( const std::string &c, URLEncodeType enctype)
|
||||
{
|
||||
|
||||
std::string escaped;
|
||||
int max = c.length();
|
||||
for(int i=0; i<max; i++)
|
||||
{
|
||||
// Unreserved chars
|
||||
if ( (48 <= c[i] && c[i] <= 57) ||//0-9
|
||||
(65 <= c[i] && c[i] <= 90) ||//ABC...XYZ
|
||||
(97 <= c[i] && c[i] <= 122) || //abc...xyz
|
||||
(c[i]=='~' || c[i]=='-' || c[i]=='_' || c[i]=='.')
|
||||
)
|
||||
{
|
||||
escaped.append( &c[i], 1);
|
||||
}
|
||||
else if (c[i] != ':' && c[i] != '/' && c[i] != '?' && c[i] != '#' &&
|
||||
c[i] != '[' && c[i] != ']' && c[i] != '@' && c[i] != '%' &&
|
||||
|
||||
c[i] != '!' && c[i] != '$' && c[i] != '&' && c[i] != '\'' &&
|
||||
c[i] != '(' && c[i] != ')' && c[i] != '*' && c[i] != '+' &&
|
||||
c[i] != ',' && c[i] != ';' && c[i] != '=')
|
||||
{
|
||||
// Characters not in unreserved (first if block) and not in
|
||||
// the reserved set are always encoded.
|
||||
escaped.append("%");
|
||||
escaped.append( char2hex(c[i]) );//converts char 255 to string "FF"
|
||||
}
|
||||
else
|
||||
{
|
||||
// Finally, the reserved set. Encoding here depends on the
|
||||
// context (where in the URI we are, what type of URI, and
|
||||
// which character).
|
||||
|
||||
bool enc = false;
|
||||
|
||||
// Always encode reserved gen-delims + '%' (which always
|
||||
// needs encoding
|
||||
if (c[i] == ':' || c[i] == '/' || c[i] == '?' || c[i] == '#' ||
|
||||
c[i] == '[' || c[i] == ']' || c[i] == '@' || c[i] == '%')
|
||||
{
|
||||
enc = true;
|
||||
}
|
||||
else {
|
||||
switch (enctype) {
|
||||
case URLEncode_Everything:
|
||||
enc = true;
|
||||
break;
|
||||
case URLEncode_Path:
|
||||
// Only reserved sub-delim that needs encoding is %,
|
||||
// taken care of above. Otherwise, leave unencoded
|
||||
enc = false;
|
||||
break;
|
||||
case URLEncode_QueryKey:
|
||||
if (c[i] == '&' ||
|
||||
c[i] == '+' ||
|
||||
c[i] == '=')
|
||||
enc = true;
|
||||
else
|
||||
enc = false;
|
||||
break;
|
||||
case URLEncode_QueryValue:
|
||||
if (c[i] == '&' ||
|
||||
c[i] == '+')
|
||||
enc = true;
|
||||
else
|
||||
enc = false;
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown urlencode type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (enc) {
|
||||
escaped.append("%");
|
||||
escaped.append( char2hex(c[i]) );//converts char 255 to string "FF"
|
||||
} else {
|
||||
escaped.append( &c[i], 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return escaped;
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
#ifndef __URLENCODE_H__
|
||||
#define __URLENCODE_H__
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
std::string char2hex( char dec );
|
||||
enum URLEncodeType {
|
||||
URLEncode_Everything,
|
||||
URLEncode_Path,
|
||||
URLEncode_QueryKey,
|
||||
URLEncode_QueryValue,
|
||||
};
|
||||
std::string urlencode( const std::string &c, URLEncodeType enctype );
|
||||
|
||||
#endif // __URLENCODE_H__
|
|
@ -22,3 +22,5 @@ target_compile_definitions(${PROJECT_NAME}
|
|||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE $<$<C_COMPILER_ID:Clang,AppleClang,GNU>:-Wno-unused-value>)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC ZLIB::ZLIB)
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
|
||||
#include "codearea.h"
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include <iomanip>
|
||||
#include <ios>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
@ -96,26 +97,24 @@ class StreamFlagsKeeper
|
|||
std::ios_base::fmtflags m_flags;
|
||||
};
|
||||
|
||||
|
||||
template <typename TNumber>
|
||||
constexpr bool IsChar(TNumber) noexcept
|
||||
{
|
||||
return std::is_same<signed char, TNumber>::value ||
|
||||
std::is_same<unsigned char, TNumber>::value ||
|
||||
std::is_same<char, TNumber>::value;
|
||||
};
|
||||
|
||||
template <typename TNumber, typename std::enable_if<!IsChar(TNumber{}), void*>::type = nullptr>
|
||||
void PrintPaddedNumber(std::ostream & ost, TNumber const number, uint32_t const padding = 1)
|
||||
{
|
||||
static_assert(std::is_integral<TNumber>::value, "number should be of integral type.");
|
||||
StreamFlagsKeeper keeper(ost);
|
||||
ost << std::setw(padding) << std::setfill('0') << number;
|
||||
}
|
||||
static constexpr bool isChar = std::is_same_v<signed char, TNumber> ||
|
||||
std::is_same_v<unsigned char, TNumber> ||
|
||||
std::is_same_v<char, TNumber>;
|
||||
|
||||
template <typename TNumber, typename std::enable_if<IsChar(TNumber{}), void*>::type = nullptr>
|
||||
void PrintPaddedNumber(std::ostream & ost, TNumber const number, uint32_t const padding = 1)
|
||||
{
|
||||
PrintPaddedNumber(ost, static_cast<int32_t>(number), padding);
|
||||
if constexpr (isChar)
|
||||
{
|
||||
PrintPaddedNumber(ost, static_cast<int32_t>(number), padding);
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(std::is_integral<TNumber>::value, "number should be of integral type.");
|
||||
StreamFlagsKeeper keeper(ost);
|
||||
ost << std::setw(padding) << std::setfill('0') << number;
|
||||
}
|
||||
}
|
||||
|
||||
void PrintHoursMinutes(std::ostream & ost,
|
||||
|
|
|
@ -6,13 +6,7 @@ omim_add_test(${PROJECT_NAME} ${SRC} BOOST_TEST)
|
|||
|
||||
target_link_libraries(${PROJECT_NAME} opening_hours)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
set(COPY_CMD cp -u)
|
||||
else()
|
||||
set(COPY_CMD rsync -a)
|
||||
endif()
|
||||
|
||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${COPY_CMD} "${CMAKE_CURRENT_SOURCE_DIR}/opening-count.lst" "${CMAKE_BINARY_DIR}/"
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/opening-count.lst" "${CMAKE_BINARY_DIR}/"
|
||||
COMMENT "Copying opening-count.lst file for testing"
|
||||
)
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
project(sdf_image)
|
||||
|
||||
set(SRC
|
||||
sdf_image.cpp
|
||||
sdf_image.h
|
||||
)
|
||||
|
||||
add_library(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_compile_options(${PROJECT_NAME}
|
||||
PRIVATE $<$<CXX_COMPILER_ID:AppleClang,Clang>:-Wno-shorten-64-to-32>
|
||||
)
|
|
@ -1,510 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2009 by Stefan Gustavson
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "3party/sdf_image/sdf_image.h"
|
||||
|
||||
#include "base/math.hpp"
|
||||
#include "base/scope_guard.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
using namespace std::placeholders;
|
||||
|
||||
namespace sdf_image
|
||||
{
|
||||
namespace
|
||||
{
|
||||
float const SQRT2 = 1.4142136f;
|
||||
|
||||
float ComputeXGradient(float ul, float /*u*/, float ur, float l, float r, float dl, float /*d*/, float dr)
|
||||
{
|
||||
return (ur + SQRT2 * r + dr) - (ul + SQRT2 * l + dl);
|
||||
}
|
||||
|
||||
float ComputeYGradient(float ul, float u, float ur, float /*l*/, float /*r*/, float dl, float d, float dr)
|
||||
{
|
||||
return (ur + SQRT2 * d + dr) - (ul + SQRT2 * u + dl);
|
||||
}
|
||||
|
||||
}
|
||||
#define BIND_GRADIENT(f) std::bind(&f, _1, _2, _3, _4, _5, _6, _7, _8)
|
||||
#define TRANSFORM(offset, dx, dy) \
|
||||
if (Transform(i, offset, dx, dy, xDist, yDist, oldDist)) \
|
||||
{ \
|
||||
dist.m_data[i] = oldDist; \
|
||||
changed = true; \
|
||||
}
|
||||
|
||||
|
||||
SdfImage::SdfImage(uint32_t h, uint32_t w)
|
||||
: m_height(h)
|
||||
, m_width(w)
|
||||
{
|
||||
m_data.resize(m_width * m_height, 0);
|
||||
}
|
||||
|
||||
SdfImage::SdfImage(uint32_t h, uint32_t w, uint8_t * imageData, uint8_t border)
|
||||
{
|
||||
int8_t doubleBorder = 2 * border;
|
||||
m_width = w + doubleBorder;
|
||||
m_height = h + doubleBorder;
|
||||
|
||||
uint32_t floatCount = m_width * m_height;
|
||||
m_data.resize(floatCount, 0.0f);
|
||||
for (size_t row = border; row < h + border; ++row)
|
||||
{
|
||||
size_t dstBaseIndex = row * m_width;
|
||||
size_t srcBaseIndex = (row - border) * w;
|
||||
for (size_t column = border; column < w + border; ++column)
|
||||
m_data[dstBaseIndex + column] = (float)imageData[srcBaseIndex + column - border] / 255.0f;
|
||||
}
|
||||
}
|
||||
|
||||
SdfImage::SdfImage(SdfImage const & copy)
|
||||
{
|
||||
m_height = copy.m_height;
|
||||
m_width = copy.m_width;
|
||||
m_data = copy.m_data;
|
||||
}
|
||||
|
||||
uint32_t SdfImage::GetWidth() const
|
||||
{
|
||||
return m_width;
|
||||
}
|
||||
|
||||
uint32_t SdfImage::GetHeight() const
|
||||
{
|
||||
return m_height;
|
||||
}
|
||||
|
||||
void SdfImage::GetData(std::vector<uint8_t> & dst)
|
||||
{
|
||||
ASSERT(m_data.size() <= dst.size(), ());
|
||||
std::transform(m_data.begin(), m_data.end(), dst.begin(), [](float const & node)
|
||||
{
|
||||
return static_cast<uint8_t>(node * 255.0f);
|
||||
});
|
||||
}
|
||||
|
||||
void SdfImage::Scale()
|
||||
{
|
||||
float maxi = std::numeric_limits<float>::min();
|
||||
float mini = std::numeric_limits<float>::max();
|
||||
|
||||
std::for_each(m_data.begin(), m_data.end(), [&maxi, &mini](float const & node)
|
||||
{
|
||||
maxi = std::max(maxi, node);
|
||||
mini = std::min(mini, node);
|
||||
});
|
||||
|
||||
maxi -= mini;
|
||||
std::for_each(m_data.begin(), m_data.end(), [&maxi, &mini](float & node)
|
||||
{
|
||||
node = (node - mini) / maxi;
|
||||
});
|
||||
}
|
||||
|
||||
void SdfImage::Invert()
|
||||
{
|
||||
std::for_each(m_data.begin(), m_data.end(), [](float & node)
|
||||
{
|
||||
node = 1.0f - node;
|
||||
});
|
||||
}
|
||||
|
||||
void SdfImage::Minus(SdfImage & im)
|
||||
{
|
||||
ASSERT(m_data.size() == im.m_data.size(), ());
|
||||
std::transform(m_data.begin(), m_data.end(), im.m_data.begin(), m_data.begin(), [](float const & n1, float const & n2)
|
||||
{
|
||||
return n1 - n2;
|
||||
});
|
||||
}
|
||||
|
||||
void SdfImage::Distquant()
|
||||
{
|
||||
std::for_each(m_data.begin(), m_data.end(), [](float & node)
|
||||
{
|
||||
node = base::Clamp(0.5f + node * 0.0325f, 0.0f, 1.0f);
|
||||
});
|
||||
}
|
||||
|
||||
void SdfImage::GenerateSDF(float sc)
|
||||
{
|
||||
Scale();
|
||||
|
||||
SdfImage outside(m_height, m_width);
|
||||
SdfImage inside(m_height, m_width);
|
||||
|
||||
size_t shortCount = m_width * m_height;
|
||||
std::vector<short> xDist;
|
||||
std::vector<short> yDist;
|
||||
xDist.resize(shortCount, 0);
|
||||
yDist.resize(shortCount, 0);
|
||||
|
||||
MexFunction(*this, xDist, yDist, outside);
|
||||
|
||||
fill(xDist.begin(), xDist.end(), 0);
|
||||
fill(yDist.begin(), yDist.end(), 0);
|
||||
|
||||
Invert();
|
||||
MexFunction(*this, xDist, yDist, inside);
|
||||
|
||||
outside.Minus(inside);
|
||||
outside.Distquant();
|
||||
outside.Invert();
|
||||
*this = outside.Bilinear(sc);
|
||||
}
|
||||
|
||||
SdfImage SdfImage::Bilinear(float scale)
|
||||
{
|
||||
uint32_t srcWidth = GetWidth();
|
||||
uint32_t srcHeight = GetHeight();
|
||||
uint32_t dstWidth = std::round(srcWidth * scale);
|
||||
uint32_t dstHeight = std::round(srcHeight * scale);
|
||||
|
||||
SdfImage result(dstHeight, dstWidth);
|
||||
|
||||
float xRatio = static_cast<float>(srcWidth) / dstWidth;
|
||||
float yRatio = static_cast<float>(srcHeight) / dstHeight;
|
||||
for (uint32_t i = 0; i < dstHeight; i++)
|
||||
{
|
||||
uint32_t baseIndex = i * dstWidth;
|
||||
for (uint32_t j = 0; j < dstWidth; j++)
|
||||
{
|
||||
float fx = xRatio * j;
|
||||
float fy = yRatio * i;
|
||||
uint32_t x = static_cast<uint32_t>(fx);
|
||||
uint32_t y = static_cast<uint32_t>(fy);
|
||||
uint32_t index = y * srcWidth + x;
|
||||
ASSERT_LESS(index, m_data.size(), ());
|
||||
|
||||
// range is 0 to 255 thus bitwise AND with 0xff
|
||||
float A = m_data[index];
|
||||
float B = m_data[index + 1];
|
||||
float C = m_data[index + srcWidth];
|
||||
float D = m_data[index + srcWidth + 1];
|
||||
|
||||
float xDiff = fx - x;
|
||||
float yDiff = fy - y;
|
||||
float xInvertDiff = 1.0f - xDiff;
|
||||
float yInvertDiff = 1.0f - yDiff;
|
||||
|
||||
float gray = A * xInvertDiff * yInvertDiff + B * xDiff * yInvertDiff +
|
||||
C * xInvertDiff * yDiff + D * xDiff * yDiff;
|
||||
|
||||
result.m_data[baseIndex + j] = gray;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
float SdfImage::ComputeGradient(uint32_t x, uint32_t y, SdfImage::TComputeFn const & fn) const
|
||||
{
|
||||
if (x < 1 || x > m_width - 1 ||
|
||||
y < 1 || y > m_height - 1)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
size_t k = y * m_width + x;
|
||||
|
||||
uint32_t l = k - 1;
|
||||
uint32_t r = k + 1;
|
||||
uint32_t u = k - m_width;
|
||||
uint32_t d = k + m_width;
|
||||
uint32_t ul = u - 1;
|
||||
uint32_t dl = d -1;
|
||||
uint32_t ur = u + 1;
|
||||
uint32_t dr = d + 1;
|
||||
|
||||
if (m_data[k] > 0.0 && m_data[k] < 1.0)
|
||||
{
|
||||
return fn(m_data[ul], m_data[u], m_data[ur],
|
||||
m_data[l], m_data[r],
|
||||
m_data[dl], m_data[d], m_data[dr]);
|
||||
}
|
||||
else
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
void SdfImage::MexFunction(SdfImage const & img, std::vector<short> & xDist, std::vector<short> & yDist, SdfImage & out)
|
||||
{
|
||||
ASSERT_EQUAL(img.GetWidth(), out.GetWidth(), ());
|
||||
ASSERT_EQUAL(img.GetHeight(), out.GetHeight(), ());
|
||||
|
||||
img.EdtaA3(xDist, yDist, out);
|
||||
// Pixels with grayscale>0.5 will have a negative distance.
|
||||
// This is correct, but we don't want values <0 returned here.
|
||||
std::for_each(out.m_data.begin(), out.m_data.end(), [](float & n)
|
||||
{
|
||||
n = std::max(0.0f, n);
|
||||
});
|
||||
}
|
||||
|
||||
float SdfImage::DistaA3(int c, int xc, int yc, int xi, int yi) const
|
||||
{
|
||||
int closest = c - xc - yc * m_width; // Index to the edge pixel pointed to from c
|
||||
//if (closest < 0 || closest > m_data.size())
|
||||
// return 1000000.0;
|
||||
ASSERT_GREATER_OR_EQUAL(closest, 0, ());
|
||||
ASSERT_LESS(closest, m_data.size(), ());
|
||||
|
||||
float a = base::Clamp(m_data[closest], 0.0f, 1.0f); // Grayscale value at the edge pixel
|
||||
|
||||
if(a == 0.0)
|
||||
return 1000000.0; // Not an object pixel, return "very far" ("don't know yet")
|
||||
|
||||
double dx = static_cast<double>(xi);
|
||||
double dy = static_cast<double>(yi);
|
||||
double di = sqrt(dx * dx + dy * dy); // Length of integer vector, like a traditional EDT
|
||||
double df = 0.0;
|
||||
if(di == 0.0)
|
||||
{
|
||||
int y = closest / m_width;
|
||||
int x = closest % m_width;
|
||||
// Use local gradient only at edges
|
||||
// Estimate based on local gradient only
|
||||
df = EdgeDf(ComputeGradient(x, y, BIND_GRADIENT(ComputeXGradient)),
|
||||
ComputeGradient(x, y, BIND_GRADIENT(ComputeYGradient)), a);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Estimate gradient based on direction to edge (accurate for large di)
|
||||
df = EdgeDf(dx, dy, a);
|
||||
}
|
||||
return static_cast<float>(di + df); // Same metric as edtaa2, except at edges (where di=0)
|
||||
}
|
||||
|
||||
double SdfImage::EdgeDf(double gx, double gy, double a) const
|
||||
{
|
||||
double df = 0.0;
|
||||
|
||||
if ((gx == 0) || (gy == 0))
|
||||
{
|
||||
// Either A) gu or gv are zero
|
||||
// B) both
|
||||
df = 0.5 - a; // Linear approximation is A) correct or B) a fair guess
|
||||
}
|
||||
else
|
||||
{
|
||||
double glength = sqrt(gx * gx + gy * gy);
|
||||
if(glength > 0)
|
||||
{
|
||||
gx = gx / glength;
|
||||
gy = gy / glength;
|
||||
}
|
||||
|
||||
// Everything is symmetric wrt sign and transposition,
|
||||
// so move to first octant (gx>=0, gy>=0, gx>=gy) to
|
||||
// avoid handling all possible edge directions.
|
||||
|
||||
gx = fabs(gx);
|
||||
gy = fabs(gy);
|
||||
if (gx < gy)
|
||||
std::swap(gx, gy);
|
||||
|
||||
double a1 = 0.5 * gy / gx;
|
||||
if (a < a1)
|
||||
df = 0.5 * (gx + gy) - sqrt(2.0 * gx * gy * a);
|
||||
else if (a < (1.0 - a1))
|
||||
df = (0.5 - a) * gx;
|
||||
else
|
||||
df = -0.5 * (gx + gy) + sqrt(2.0 * gx * gy * (1.0 - a));
|
||||
}
|
||||
|
||||
return df;
|
||||
}
|
||||
|
||||
void SdfImage::EdtaA3(std::vector<short> & xDist, std::vector<short> & yDist, SdfImage & dist) const
|
||||
{
|
||||
ASSERT_EQUAL(dist.GetHeight(), GetHeight(), ());
|
||||
ASSERT_EQUAL(dist.GetWidth(), GetWidth(), ());
|
||||
ASSERT_EQUAL(dist.m_data.size(), m_data.size(), ());
|
||||
|
||||
int w = GetWidth();
|
||||
int h = GetHeight();
|
||||
|
||||
/* Initialize the distance SdfImages */
|
||||
for (size_t y = 0; y < h; ++y)
|
||||
{
|
||||
size_t baseIndex = y * w;
|
||||
for (size_t x = 0; x < w; ++x)
|
||||
{
|
||||
size_t index = baseIndex + x;
|
||||
if (m_data[index] <= 0.0)
|
||||
dist.m_data[index]= 1000000.0; // Big value, means "not set yet"
|
||||
else if (m_data[index] < 1.0)
|
||||
{
|
||||
dist.m_data[index] = EdgeDf(ComputeGradient(x, y, BIND_GRADIENT(ComputeXGradient)),
|
||||
ComputeGradient(x, y, BIND_GRADIENT(ComputeYGradient)),
|
||||
m_data[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize index offsets for the current SdfImage width */
|
||||
int offsetU = -w;
|
||||
int offsetD = w;
|
||||
int offsetR = 1;
|
||||
int offsetL = -1;
|
||||
int offsetRu = -w + 1;
|
||||
int offsetRd = w + 1;
|
||||
int offsetLd = w - 1;
|
||||
int offsetLu = -w - 1;
|
||||
|
||||
/* Perform the transformation */
|
||||
bool changed;
|
||||
do
|
||||
{
|
||||
changed = false;
|
||||
for(int y = 1; y < h; ++y)
|
||||
{
|
||||
int i = y * w;
|
||||
|
||||
/* scan right, propagate distances from above & left */
|
||||
/* Leftmost pixel is special, has no left neighbors */
|
||||
float oldDist = dist.m_data[i];
|
||||
if(oldDist > 0) // If non-zero distance or not set yet
|
||||
{
|
||||
TRANSFORM(offsetU, 0, 1);
|
||||
TRANSFORM(offsetRu, -1, 1);
|
||||
}
|
||||
|
||||
++i;
|
||||
|
||||
/* Middle pixels have all neighbors */
|
||||
for(int x = 1; x < w - 1; ++x, ++i)
|
||||
{
|
||||
oldDist = dist.m_data[i];
|
||||
if(oldDist > 0.0)
|
||||
{
|
||||
TRANSFORM(offsetL, 1, 0);
|
||||
TRANSFORM(offsetLu, 1, 1);
|
||||
TRANSFORM(offsetU, 0, 1);
|
||||
TRANSFORM(offsetRu, -1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Rightmost pixel of row is special, has no right neighbors */
|
||||
oldDist = dist.m_data[i];
|
||||
if(oldDist > 0)
|
||||
{
|
||||
TRANSFORM(offsetL, 1, 0);
|
||||
TRANSFORM(offsetLu, 1, 1);
|
||||
TRANSFORM(offsetU, 0, 1);
|
||||
}
|
||||
|
||||
/* Move index to second rightmost pixel of current row. */
|
||||
/* Rightmost pixel is skipped, it has no right neighbor. */
|
||||
i = y * w + w - 2;
|
||||
|
||||
/* scan left, propagate distance from right */
|
||||
for(int x = w - 2; x >= 0; --x, --i)
|
||||
{
|
||||
oldDist = dist.m_data[i];
|
||||
if(oldDist > 0.0)
|
||||
TRANSFORM(offsetR, -1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Scan rows in reverse order, except last row */
|
||||
for(int y = h - 2; y >= 0; --y)
|
||||
{
|
||||
/* move index to rightmost pixel of current row */
|
||||
int i = y * w + w - 1;
|
||||
|
||||
/* Scan left, propagate distances from below & right */
|
||||
|
||||
/* Rightmost pixel is special, has no right neighbors */
|
||||
float oldDist = dist.m_data[i];
|
||||
if(oldDist > 0) // If not already zero distance
|
||||
{
|
||||
TRANSFORM(offsetD, 0, -1);
|
||||
TRANSFORM(offsetLd, 1, -1);
|
||||
}
|
||||
|
||||
--i;
|
||||
|
||||
/* Middle pixels have all neighbors */
|
||||
for(int x = w - 2; x > 0; --x, --i)
|
||||
{
|
||||
oldDist = dist.m_data[i];
|
||||
if(oldDist > 0.0)
|
||||
{
|
||||
TRANSFORM(offsetR, -1, 0);
|
||||
TRANSFORM(offsetRd, -1, -1);
|
||||
TRANSFORM(offsetD, 0, -1);
|
||||
TRANSFORM(offsetLd, 1, -1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Leftmost pixel is special, has no left neighbors */
|
||||
oldDist = dist.m_data[i];
|
||||
if(oldDist > 0)
|
||||
{
|
||||
TRANSFORM(offsetR, -1, 0);
|
||||
TRANSFORM(offsetRd, -1, -1);
|
||||
TRANSFORM(offsetD, 0, -1);
|
||||
}
|
||||
|
||||
/* Move index to second leftmost pixel of current row. */
|
||||
/* Leftmost pixel is skipped, it has no left neighbor. */
|
||||
i = y * w + 1;
|
||||
for(int x = 1; x < w; ++x, ++i)
|
||||
{
|
||||
/* scan right, propagate distance from left */
|
||||
oldDist = dist.m_data[i];
|
||||
if(oldDist > 0.0)
|
||||
TRANSFORM(offsetL, 1, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
while(changed);
|
||||
}
|
||||
|
||||
bool SdfImage::Transform(int baseIndex, int offset, int dx, int dy, std::vector<short> & xDist, std::vector<short> & yDist, float & oldDist) const
|
||||
{
|
||||
double const epsilon = 1e-3;
|
||||
ASSERT_EQUAL(xDist.size(), yDist.size(), ());
|
||||
ASSERT_GREATER_OR_EQUAL(baseIndex, 0, ());
|
||||
ASSERT_LESS(baseIndex, xDist.size(), ());
|
||||
|
||||
int candidate = baseIndex + offset;
|
||||
ASSERT_GREATER_OR_EQUAL(candidate, 0, ());
|
||||
ASSERT_LESS(candidate, xDist.size(), ());
|
||||
|
||||
int cDistX = xDist[candidate];
|
||||
int cDistY = yDist[candidate];
|
||||
int newDistX = cDistX + dx;
|
||||
int newDistY = cDistY + dy;
|
||||
float newDist = DistaA3(candidate, cDistX, cDistY, newDistX, newDistY);
|
||||
if(newDist < oldDist - epsilon)
|
||||
{
|
||||
xDist[baseIndex] = newDistX;
|
||||
yDist[baseIndex] = newDistY;
|
||||
oldDist = newDist;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
} // namespace sdf_image
|
|
@ -1,81 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
// +----------------------------------------+
|
||||
// | |
|
||||
// | http://contourtextures.wikidot.com |
|
||||
// | |
|
||||
// +----------------------------------------+
|
||||
|
||||
/*
|
||||
Copyright (C) 2009 by Stefan Gustavson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "base/buffer_vector.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace sdf_image
|
||||
{
|
||||
class SdfImage
|
||||
{
|
||||
public:
|
||||
SdfImage() = default;
|
||||
SdfImage(uint32_t h, uint32_t w);
|
||||
SdfImage(uint32_t h, uint32_t w, uint8_t * imageData, uint8_t border);
|
||||
SdfImage(SdfImage const & copy);
|
||||
|
||||
uint32_t GetWidth() const;
|
||||
uint32_t GetHeight() const;
|
||||
void GetData(std::vector<uint8_t> & dst);
|
||||
void GenerateSDF(float sc);
|
||||
|
||||
private:
|
||||
void Scale();
|
||||
void Invert();
|
||||
void Minus(SdfImage &im);
|
||||
void Distquant();
|
||||
SdfImage Bilinear(float Scale);
|
||||
|
||||
private:
|
||||
/// ul = up left
|
||||
/// u = up
|
||||
/// ...
|
||||
/// d = down
|
||||
/// dr = down right
|
||||
/// ul u ur l r dl d dr
|
||||
using TComputeFn = std::function<float (float, float, float, float, float, float, float, float)>;
|
||||
float ComputeGradient(uint32_t x, uint32_t y, TComputeFn const & fn) const;
|
||||
void MexFunction(SdfImage const & img, std::vector<short> & xDist, std::vector<short> & yDist,
|
||||
SdfImage & out);
|
||||
float DistaA3(int c, int xc, int yc, int xi, int yi) const;
|
||||
double EdgeDf(double gx, double gy, double a) const;
|
||||
void EdtaA3(std::vector<short> & xDist, std::vector<short> & yDist, SdfImage & dist) const;
|
||||
bool Transform(int baseIndex, int offset, int dx, int dy, std::vector<short> & xDist,
|
||||
std::vector<short> & yDist, float & oldDist) const;
|
||||
|
||||
private:
|
||||
uint32_t m_height = 0;
|
||||
uint32_t m_width = 0;
|
||||
buffer_vector<float, 512> m_data;
|
||||
};
|
||||
} // namespace sdf_image
|
|
@ -38,14 +38,14 @@ namespace succinct {
|
|||
|
||||
typedef std::vector<uint64_t> bits_type;
|
||||
|
||||
bit_vector_builder(uint64_t size = 0, bool init = 0)
|
||||
bit_vector_builder(uint64_t size = 0, bool initBit = false)
|
||||
: m_size(size)
|
||||
{
|
||||
m_bits.resize(detail::words_for(size), uint64_t(-init));
|
||||
m_bits.resize(detail::words_for(size), initBit ? uint64_t(-1) : 0);
|
||||
if (size) {
|
||||
m_cur_word = &m_bits.back();
|
||||
// clear padding bits
|
||||
if (init && size % 64) {
|
||||
if (initBit && size % 64) {
|
||||
*m_cur_word >>= 64 - (size % 64);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
../../utfcpp/source
|
|
@ -1 +1 @@
|
|||
Subproject commit 6f0e7c7865208f2a6b882a7e138584beb1b6b2fd
|
||||
Subproject commit 6be08bbea14ffa0a5c594257fb6285a054395cd7
|
112
CMakeLists.txt
112
CMakeLists.txt
|
@ -7,6 +7,15 @@ set(CMAKE_C_STANDARD 11)
|
|||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_C_EXTENSIONS OFF)
|
||||
|
||||
set(CMAKE_C_VISIBILITY_PRESET hidden)
|
||||
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
|
||||
set(CMAKE_VISIBILITY_INLINES_HIDDEN ON)
|
||||
|
||||
# Fixes warning ld: warning: ignoring duplicate libraries on Mac and Windows.
|
||||
if (POLICY CMP0156)
|
||||
cmake_policy(SET CMP0156 NEW)
|
||||
endif()
|
||||
|
||||
if (APPLE AND NOT ("${CMAKE_SYSTEM_NAME}" STREQUAL Android))
|
||||
# OBJC/OBJCXX are needed to skip m/mm files in Unity builds.
|
||||
# https://gitlab.kitware.com/cmake/cmake/-/issues/21963
|
||||
|
@ -14,10 +23,12 @@ if (APPLE AND NOT ("${CMAKE_SYSTEM_NAME}" STREQUAL Android))
|
|||
set(CMAKE_OBJC_EXTENSIONS OFF)
|
||||
set(CMAKE_OBJC_STANDARD 11)
|
||||
set(CMAKE_OBJC_FLAGS -fobjc-arc)
|
||||
set(CMAKE_OBJC_VISIBILITY_PRESET hidden)
|
||||
enable_language(OBJCXX)
|
||||
set(CMAKE_OBJCXX_EXTENSIONS OFF)
|
||||
set(CMAKE_OBJCXX_STANDARD 17)
|
||||
set(CMAKE_OBJCXX_FLAGS -fobjc-arc)
|
||||
set(CMAKE_OBJCXX_VISIBILITY_PRESET hidden)
|
||||
endif()
|
||||
|
||||
message(STATUS "Using compiler ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}")
|
||||
|
@ -62,32 +73,23 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${OMIM_ROOT}/cmake")
|
|||
include(OmimHelpers)
|
||||
include(OmimTesting)
|
||||
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
set(LINUX_DETECTED TRUE)
|
||||
endif()
|
||||
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Android")
|
||||
set(ANDROID_DETECTED TRUE)
|
||||
if ((${OS} MATCHES "mac"))
|
||||
set(DARWIN TRUE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
omim_set_platform_var(PLATFORM_IPHONE "iphone-.*")
|
||||
omim_set_platform_var(PLATFORM_ANDROID "android-.*" ${ANDROID_DETECTED})
|
||||
omim_set_platform_var(PLATFORM_MAC "macx-.*" ${APPLE})
|
||||
omim_set_platform_var(PLATFORM_WIN "win32-.*" ${WIN32})
|
||||
omim_set_platform_var(PLATFORM_LINUX "linux-.*" ${LINUX_DETECTED})
|
||||
|
||||
if (PLATFORM_LINUX OR PLATFORM_MAC OR PLATFORM_WIN)
|
||||
set(PLATFORM_DESKTOP TRUE)
|
||||
else()
|
||||
set(PLATFORM_DESKTOP TRUE)
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set(PLATFORM_LINUX TRUE)
|
||||
elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
set(PLATFORM_MAC TRUE)
|
||||
elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
set(PLATFORM_WIN TRUE)
|
||||
elseif (CMAKE_SYSTEM_NAME STREQUAL "Android")
|
||||
set(PLATFORM_ANDROID TRUE)
|
||||
set(PLATFORM_DESKTOP FALSE)
|
||||
elseif (CMAKE_SYSTEM_NAME STREQUAL "iOS")
|
||||
set(PLATFORM_IPHONE TRUE)
|
||||
set(PLATFORM_DESKTOP FALSE)
|
||||
else()
|
||||
message(FATAL_ERROR "Unsupported platform: ${CMAKE_SYSTEM_NAME}")
|
||||
endif()
|
||||
|
||||
# End of setting the target platform
|
||||
|
||||
# Sanitizer
|
||||
if (PLATFORM_DESKTOP)
|
||||
# https://clang.llvm.org/docs/UsersManual.html#controlling-code-generation
|
||||
|
@ -106,14 +108,31 @@ if (NOT CMAKE_BUILD_TYPE)
|
|||
endif()
|
||||
|
||||
# Global compile options for all configurations.
|
||||
add_compile_options(-ffast-math)
|
||||
if (MSVC)
|
||||
add_compile_options(/utf-8)
|
||||
add_link_options(/INCREMENTAL:NO)
|
||||
else()
|
||||
add_compile_options(-ffast-math)
|
||||
endif()
|
||||
|
||||
if (PLATFORM_WIN)
|
||||
add_definitions(
|
||||
-DWIN32_LEAN_AND_MEAN
|
||||
-DNOMINMAX
|
||||
)
|
||||
endif()
|
||||
|
||||
# Built-in CMake configurations: Debug, Release, RelWithDebInfo, MinSizeRel
|
||||
if (${CMAKE_BUILD_TYPE} STREQUAL "Debug")
|
||||
add_definitions(-DDEBUG -fno-omit-frame-pointer)
|
||||
add_definitions(-DDEBUG)
|
||||
if (NOT MSVC)
|
||||
add_compile_options(-fno-omit-frame-pointer)
|
||||
endif()
|
||||
elseif (${CMAKE_BUILD_TYPE} MATCHES "Rel")
|
||||
add_definitions(-DRELEASE)
|
||||
add_compile_options(-Ofast)
|
||||
if (NOT MSVC)
|
||||
add_compile_options(-Ofast) # Also enables -ffast-math
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR "Unknown build type: " ${CMAKE_BUILD_TYPE})
|
||||
endif()
|
||||
|
@ -135,10 +154,9 @@ option(USE_TSAN "Enable Thread Sanitizer" OFF)
|
|||
option(USE_LIBFUZZER "Enable LibFuzzer" OFF)
|
||||
option(PYBINDINGS "Create makefiles for building python bindings" OFF)
|
||||
option(SKIP_QT_GUI "Skip building of Qt GUI" OFF)
|
||||
# TODO: Fix mapshot, it doesn't work without our old FreeType hack.
|
||||
option(BUILD_MAPSHOT "Build mapshot tool" OFF)
|
||||
option(USE_PCH "Use precompiled headers" OFF)
|
||||
option(NJOBS "Number of parallel processes" OFF)
|
||||
option(ENABLE_VULKAN_DIAGNOSTICS "Enable Vulkan diagnostics" OFF)
|
||||
|
||||
if (NJOBS)
|
||||
message(STATUS "Number of parallel processes: ${NJOBS}")
|
||||
|
@ -190,6 +208,11 @@ if (USE_HEAPPROF)
|
|||
message(STATUS "Heap Profiler is enabled")
|
||||
endif()
|
||||
|
||||
if (ENABLE_VULKAN_DIAGNOSTICS)
|
||||
message(WARNING "Vulkan diagnostics are enabled. Be aware of performance impact!")
|
||||
add_definitions(-DENABLE_VULKAN_DIAGNOSTICS)
|
||||
endif()
|
||||
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
# Set environment variables
|
||||
|
@ -252,11 +275,6 @@ if (NOT PLATFORM_IPHONE AND NOT PLATFORM_ANDROID)
|
|||
find_package(Qt6 COMPONENTS REQUIRED ${qt_components} PATHS $ENV{QT_PATH} /opt/homebrew/opt/qt@6 /usr/local/opt/qt@6 /usr/lib/x86_64-linux-gnu/qt6)
|
||||
endif()
|
||||
|
||||
find_library(LIBZ NAMES z)
|
||||
if (LIBZ STREQUAL "LIBZ-NOTFOUND")
|
||||
message(FATAL_ERROR "Failed to find libz library.")
|
||||
endif()
|
||||
|
||||
# To allow #include "base/file_name.hpp" in all sources.
|
||||
include_directories(${CMAKE_HOME_DIRECTORY})
|
||||
|
||||
|
@ -292,12 +310,24 @@ if (USE_PCH)
|
|||
endif()
|
||||
|
||||
# Should be on the root level, not in 3party, so tests can get these dependencies.
|
||||
# Should go before 3party as harfbuzz is using them.
|
||||
if (LINUX_DETECTED)
|
||||
if (PLATFORM_LINUX OR PLATFORM_WIN)
|
||||
find_package(ICU COMPONENTS uc i18n data REQUIRED)
|
||||
find_package(Freetype REQUIRED)
|
||||
find_package(harfbuzz REQUIRED)
|
||||
endif()
|
||||
|
||||
if (WITH_SYSTEM_PROVIDED_3PARTY)
|
||||
set(GFLAGS_USE_TARGET_NAMESPACE ON)
|
||||
find_package(gflags REQUIRED)
|
||||
|
||||
find_package(expat CONFIG REQUIRED)
|
||||
find_package(jansson CONFIG REQUIRED)
|
||||
find_package(pugixml REQUIRED)
|
||||
find_package(utf8cpp REQUIRED)
|
||||
endif()
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
|
||||
# Include 3party dependencies.
|
||||
add_subdirectory(3party)
|
||||
|
||||
|
@ -307,12 +337,8 @@ if (PLATFORM_DESKTOP AND NOT WITH_SYSTEM_PROVIDED_3PARTY)
|
|||
include_directories("${PROJECT_BINARY_DIR}/3party/gflags/include")
|
||||
endif()
|
||||
|
||||
find_package(Python3 COMPONENTS Interpreter)
|
||||
if (Python3_Interpreter_FOUND)
|
||||
message(STATUS "Found python to use in qt/, shaders/ and 3party/: ${Python3_EXECUTABLE}")
|
||||
else()
|
||||
message(FATAL_ERROR "Could not find python3 to use in qt/, shaders/ and 3party/.")
|
||||
endif()
|
||||
# Used in qt/ and shaders/
|
||||
find_package(Python3 REQUIRED COMPONENTS Interpreter)
|
||||
|
||||
add_subdirectory(base)
|
||||
add_subdirectory(coding)
|
||||
|
@ -338,10 +364,6 @@ add_subdirectory(traffic)
|
|||
add_subdirectory(transit)
|
||||
|
||||
if (PLATFORM_DESKTOP)
|
||||
if (BUILD_MAPSHOT)
|
||||
add_subdirectory(mapshot)
|
||||
add_subdirectory(software_renderer)
|
||||
endif()
|
||||
omim_add_tool_subdirectory(feature_list)
|
||||
add_subdirectory(generator)
|
||||
add_subdirectory(openlr)
|
||||
|
|
31
CONTRIBUTORS
31
CONTRIBUTORS
|
@ -7,6 +7,28 @@ Original MAPS.ME (MapsWithMe) design and implementation:
|
|||
Viktor Govako <viktor.govako@gmail.com>
|
||||
Siarhei Rachytski <siarhei.rachytski@gmail.com>
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Organic Maps contributions:
|
||||
--------------------------------------------------------------------------------
|
||||
Alexander Borsuk <me@alex.bio>
|
||||
Roman Tsisyk <roman@tsisyk.com>
|
||||
Viktor Govako <viktor.govako@gmail.com>
|
||||
Caspar Nuël <casparnuel@yandex.com>
|
||||
Konstantin Pastbin
|
||||
Nishant Bhandari <nishantbhandari0019@gmail.com>
|
||||
Sebastiao Sousa <sebastiao.sousa@tecnico.ulisboa.pt>
|
||||
|
||||
Organic Maps translations:
|
||||
Karina Kordon
|
||||
Konstantin Pastbin
|
||||
Metehan Özyürek
|
||||
Joan Montané
|
||||
Luna Rose
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
MAPS.ME contributions (before Organic Maps was forked in 2020-2021):
|
||||
--------------------------------------------------------------------------------
|
||||
Code contributions:
|
||||
Dmitry Yunitski
|
||||
Lev Dragunov
|
||||
|
@ -35,10 +57,6 @@ Code contributions:
|
|||
Alex Gontmakher <gsasha@gmail.com>
|
||||
Dima Korolev <dmitry.korolev@gmail.com>
|
||||
Max Grigorev <forwidur@gmail.com>
|
||||
Roman Tsisyk <roman@tsisyk.com>
|
||||
Caspar Nuël <casparnuel@yandex.com>
|
||||
Konstantin Pastbin
|
||||
Nishant Bhandari <nishantbhandari0019@gmail.com>
|
||||
|
||||
Porting to Tizen platform:
|
||||
Sergey Pisarchik
|
||||
|
@ -62,11 +80,6 @@ Strings and translations:
|
|||
Vasily Korotkevich
|
||||
Mark N. Kuramochi
|
||||
Lidia Vasiljeva
|
||||
Karina Kordon
|
||||
Konstantin Pastbin
|
||||
Metehan Özyürek
|
||||
Joan Montané
|
||||
Luna Rose
|
||||
|
||||
Project management:
|
||||
Alexander Matveenko
|
||||
|
|
54
README.md
54
README.md
|
@ -19,12 +19,6 @@ No ads, no tracking, no data collection, no crapware. Your [donations](https://o
|
|||
<a href="https://f-droid.org/en/packages/app.organicmaps/">
|
||||
<img alt="Get it on F-Droid" src="docs/badges/fdroid.png" width="180">
|
||||
</a>
|
||||
<a href='https://flathub.org/apps/details/app.organicmaps.desktop'>
|
||||
<img alt="Download on Flathub" src="docs/badges/flathub.png" width="180"/>
|
||||
</a>
|
||||
<a href="https://repology.org/project/organicmaps/versions">
|
||||
<img src="https://repology.org/badge/vertical-allrepos/organicmaps.svg" width="180" alt="Packaging status">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p float="left">
|
||||
|
@ -102,11 +96,46 @@ Reject surveillance - embrace your freedom.
|
|||
|
||||
The app is free for everyone, so we rely on donations. Please donate at [organicmaps.app/donate](https://organicmaps.app/donate) to support us!
|
||||
|
||||
### Our sponsors
|
||||
Beloved institutional sponsors below have provided targeted grants to cover some infrastructure costs and fund development of new selected features:
|
||||
|
||||
[Mythic Beasts](https://www.mythic-beasts.com/) ISP [provides us](https://www.mythic-beasts.com/blog/2021/10/06/improving-the-world-bit-by-expensive-bit/)
|
||||
two virtual servers with 400 TB/month of free bandwidth to host and serve
|
||||
maps downloads and updates.
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://nlnet.nl/"><img src="docs/sponsors/nlnet.svg" alt="The NLnet Foundation" width="200px"></a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://github.com/organicmaps/organicmaps/milestone/7">The Search & Fonts improvement project</a> has been <a href="https://nlnet.nl/project/OrganicMaps/">funded</a> through NGI0 Entrust Fund. <a href="https://nlnet.nl/entrust/">NGI0 Entrust Fund</a> is established by the <a href="https://nlnet.nl/">NLnet Foundation</a> with financial support from the European Commission's <a href="https://www.ngi.eu/">Next Generation Internet programme</a>, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 101069594.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://summerofcode.withgoogle.com/"><img src="docs/sponsors/gsoc.svg" alt="Google Summer of Code" width="200px"></a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://summerofcode.withgoogle.com/">Google</a> backed 5 student's projects in the Google Summer of Code program during <a href="https://summerofcode.withgoogle.com/programs/2022/organizations/organic-maps">2022</a> and <a href="https://summerofcode.withgoogle.com/programs/2023/organizations/organic-maps">2023</a> programs. Noteworthy projects included Android Auto and Wikipedia Dump Extractor.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://www.mythic-beasts.com/"><img src="docs/sponsors/mythic-beasts.png" alt="Mythic Beasts" width="200px"></a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://www.mythic-beasts.com/">Mythic Beasts</a> ISP <a href="https://www.mythic-beasts.com/blog/2021/10/06/improving-the-world-bit-by-expensive-bit/">provides us</a> two virtual servers with 400 TB/month of free bandwidth to host and serve maps downloads and updates.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://futo.org"><img src="docs/sponsors/futo.svg" alt="FUTO" width="200px"></a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://futo.org">FUTO</a> has <a href="https://www.youtube.com/watch?v=fJJclgBHrEw">awarded $1000 micro-grant</a> to Organic Maps in February 2023.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
The majority of all expenses have been funded by founders of the project since its inception. The project is far from achieving any sort of financial sustainability. The current level of voluntary donations falls significantly short of covering efforts needed to sustain the app. Any new developments of features are beyond the scope of possibility due to the absence of the necessary financial resources.
|
||||
|
||||
Please consider [donating](https://organicmaps.app/donate) if you want to see this open-source project thriving, not dying. There are [other ways how to support the project](#contributing). No coding skills required.
|
||||
|
||||
## Copyrights
|
||||
|
||||
|
@ -118,7 +147,9 @@ for more information.
|
|||
|
||||
## Governance
|
||||
|
||||
See [docs/GOVERNANCE.md)](docs/GOVERNANCE.md).
|
||||
See [docs/GOVERNANCE.md](docs/GOVERNANCE.md).
|
||||
|
||||
<a name="contributing">
|
||||
|
||||
## Contributing
|
||||
|
||||
|
@ -143,6 +174,7 @@ and [Google Play](https://play.google.com/store/apps/details?id=app.organicmaps)
|
|||
- Join our [Telegram Group](https://t.me/OrganicMaps) to discuss with other users.
|
||||
- Присоединяйтесь к нашей [русскоязычной группе в Telegram](https://t.me/OrganicMapsRu) для обратной связи и помощи.
|
||||
- Diğer kullanıcılarla tartışmak için [Telegram Grubumuza](https://t.me/OrganicMapsTR) katılın.
|
||||
- Rejoignez notre groupe [Telegram](https://t.me/OrganicMapsFR) pour obtenir de l'aide.
|
||||
- Contact us by [email](mailto:hello@organicmaps.app).
|
||||
- Follow our updates in
|
||||
[Mastodon](https://fosstodon.org/@organicmaps),
|
||||
|
|
1
android/.gitignore
vendored
1
android/.gitignore
vendored
|
@ -21,3 +21,4 @@ local.properties
|
|||
.project
|
||||
lint.xml
|
||||
.gradletasknamecache
|
||||
|
||||
|
|
8
android/.idea/icon.svg
generated
Normal file
8
android/.idea/icon.svg
generated
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg version="1.1" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
|
||||
<g inkscape:groupmode="layer" inkscape:label="Layer 1">
|
||||
<rect fill="#006c35" fill-opacity="1.0" x="0" y="0" width="1024" height="1024"/>
|
||||
<path fill="#ffffff" d="m861.3562052 256.9139426c18.220574 48.4374573-79.2585233 166.2022835-172.1806378 196.4226995-168.0803471-58.2153969-173.5457836 39.5504743-311.1096159 132.4296677 162.6149552 112.4329497 332.9737882 24.8856325 329.7844002-85.7673036-127.9956457 73.3254376-208.1650824 81.3238121-254.1719349 79.1032962 154.8710218-30.6636026 322.0404219-125.7633124 357.0667834-165.7616685 0.032202 1.7749817 0.049863 3.5523114 0.049863 5.33456 0 191.0905993-295.1650572 474.6148576-295.1650572 474.6148576s-154.5086464-147.95159-239.9499331-302.4701423c-11.943516-0.1614425-83.8230022 25.9633002-110.3310391-9.051445-29.6082623-39.1049397 80.1693081-170.2028122 175.3699196-209.3102117 170.3563508 77.3261904 263.7351577-123.0971499 317.0292402-134.2068293-158.9688193-94.2135154-316.1183714-55.5493358-333.8847017 84.4353407 88.3675802-50.2172465 196.7775505-78.65765 246.8822227-76.4346744-140.7606999 28.7623957-301.9981102 132.8752019-350.2809443 172.4255642 0-159.041983 132.1505407-287.9677052 295.1650569-287.9677052 96.4018995 0 182.0095151 45.0859887 235.8744518 114.8252613 0.00335 0 96.1884344-31.2834041 109.851981 11.3787329zm-33.7059144 14.218972c-12.6607646-17.2289278-56.7341326 2.8936807-56.7341326 2.8936807 6.1678152 10.3446685 11.7002555 21.0960758 16.5352245 32.2084942 4.8995467 11.2622344 9.0867303 22.8954305 12.5044868 34.8382086 0 0 45.0041042-46.3863227 27.6944213-69.9403835zm-629.5734431 294.3797539c13.6858848 18.6189721 61.3258973-3.1309266 61.3258973-3.1309266-6.6716772-11.1802827-12.6484216-22.8033047-17.8755617-34.8139472-5.2966478-12.1727565-9.8213865-24.7454316-13.5171072-37.6545218 0 0-48.6427553 50.1397674-29.9332284 75.5993956z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2 KiB |
1
android/app/.gitignore
vendored
1
android/app/.gitignore
vendored
|
@ -12,6 +12,7 @@
|
|||
/firebase-app-distribution.json
|
||||
/firebase-test-lab.json
|
||||
/huawei-appgallery.json
|
||||
/agconnect-services.json
|
||||
/src/main/res/xml/network_security_config.xml
|
||||
|
||||
# ignore flags symlinks
|
||||
|
|
|
@ -4,9 +4,8 @@ buildscript {
|
|||
mavenCentral()
|
||||
}
|
||||
//
|
||||
// The magic below is needed to disable Google Mobile Services (a.k.a GMS) and
|
||||
// Google Firebase Services during the build time. Unfortunately, the only way
|
||||
// to disable Gradle plugins is to add these hardcore switches to buildscript().
|
||||
// The magic below is needed to disable Google Firebase Services during the build time.
|
||||
// Unfortunately, the only way to disable Gradle plugins is to add these hardcore switches to buildscript().
|
||||
//
|
||||
|
||||
// Detect flavors from the task name.
|
||||
|
@ -14,15 +13,6 @@ buildscript {
|
|||
def isFdroid = taskName.contains('fdroid')
|
||||
def isBeta = taskName.contains('beta')
|
||||
|
||||
//
|
||||
// Please add symlinks to google/java/app/organicmaps/location for each new gms-enabled flavor below:
|
||||
// ```
|
||||
// mkdir -p src/$flavor/java/app/organicmaps/
|
||||
// ln -sf ../../../../google/java/app/organicmaps/location src/$flavor/java/app/organicmaps/
|
||||
// ls -la src/$flavor/java/app/organicmaps/location/GoogleFusedLocationProvider.java
|
||||
// ```
|
||||
ext.googleMobileServicesEnabled = taskName.contains('google') || taskName.contains('huawei') || taskName.contains('web')
|
||||
|
||||
// Firebase Crashlytics compile-time feature flag: -Pfirebase=true|false
|
||||
def googleFirebaseServicesFlag = findProperty('firebase')
|
||||
// Enable Firebase for all beta flavors except fdroid only if google-services.json exists.
|
||||
|
@ -32,25 +22,19 @@ buildscript {
|
|||
googleFirebaseServicesDefault
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.2.0'
|
||||
|
||||
if (googleMobileServicesEnabled) {
|
||||
println('Building with Google Mobile Services')
|
||||
classpath 'com.google.gms:google-services:4.4.0'
|
||||
} else {
|
||||
println('Building without Google Services')
|
||||
}
|
||||
classpath 'com.android.tools.build:gradle:8.3.2'
|
||||
|
||||
if (googleFirebaseServicesEnabled) {
|
||||
println('Building with Google Firebase Services')
|
||||
classpath 'com.google.gms:google-services:4.4.1'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
|
||||
classpath 'com.google.firebase:firebase-appdistribution-gradle:4.0.1'
|
||||
classpath 'com.google.firebase:firebase-appdistribution-gradle:4.2.0'
|
||||
} else {
|
||||
println('Building without Google Firebase Services')
|
||||
}
|
||||
|
||||
classpath('com.github.triplet.gradle:play-publisher:3.8.6')
|
||||
classpath('ru.cian:huawei-publish-gradle-plugin:1.4.0')
|
||||
classpath('com.github.triplet.gradle:play-publisher:3.9.1')
|
||||
classpath('ru.cian:huawei-publish-gradle-plugin:1.4.2')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,61 +46,14 @@ repositories {
|
|||
|
||||
apply plugin: 'com.android.application'
|
||||
apply from: 'secure.properties'
|
||||
if (googleMobileServicesEnabled) {
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
}
|
||||
if (googleFirebaseServicesEnabled) {
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
apply plugin: 'com.google.firebase.appdistribution'
|
||||
}
|
||||
apply plugin: 'com.github.triplet.play'
|
||||
apply plugin: 'ru.cian.huawei-publish-gradle-plugin'
|
||||
|
||||
dependencies {
|
||||
// Google Mobile Services
|
||||
if (googleMobileServicesEnabled) {
|
||||
implementation 'com.google.android.gms:play-services-location:21.0.1'
|
||||
}
|
||||
|
||||
// Google Firebase Services
|
||||
if (googleFirebaseServicesEnabled) {
|
||||
// Import the BoM for the Firebase platform
|
||||
implementation platform('com.google.firebase:firebase-bom:32.7.0')
|
||||
// Add the dependencies for the Crashlytics and Analytics libraries
|
||||
// When using the BoM, you don't specify versions in Firebase library dependencies
|
||||
implementation 'com.google.firebase:firebase-crashlytics'
|
||||
implementation 'com.google.firebase:firebase-crashlytics-ndk'
|
||||
}
|
||||
|
||||
// This line is added as a workaround for duplicate classes error caused by some outdated dependency:
|
||||
// > A failure occurred while executing com.android.build.gradle.internal.tasks.CheckDuplicatesRunnable
|
||||
// We don't use Kotlin, but some dependencies are actively using it.
|
||||
// See https://stackoverflow.com/a/75719642
|
||||
implementation 'androidx.core:core:1.12.0'
|
||||
implementation(platform('org.jetbrains.kotlin:kotlin-bom:1.9.21'))
|
||||
implementation 'androidx.annotation:annotation:1.7.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.car.app:app:1.4.0-rc02'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.fragment:fragment:1.6.2'
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||
implementation 'androidx.work:work-runtime:2.9.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-process:2.6.2'
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
implementation 'com.google.android.material:material:1.10.0'
|
||||
// Fix for app/organicmaps/util/FileUploadWorker.java:14: error: cannot access ListenableFuture
|
||||
// https://github.com/organicmaps/organicmaps/issues/6106
|
||||
implementation 'com.google.guava:guava:32.1.3-android'
|
||||
implementation 'com.github.devnullorthrow:MPAndroidChart:3.2.0-alpha'
|
||||
implementation 'net.jcip:jcip-annotations:1.0'
|
||||
|
||||
// Test Dependencies
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.mockito:mockito-core:5.8.0'
|
||||
testImplementation 'org.mockito:mockito-inline:5.2.0'
|
||||
}
|
||||
|
||||
def run(cmd) {
|
||||
def stdout = new ByteArrayOutputStream()
|
||||
exec {
|
||||
|
@ -163,7 +100,7 @@ android {
|
|||
// All properties are read from gradle.properties file
|
||||
compileSdk propCompileSdkVersion.toInteger()
|
||||
|
||||
ndkVersion '26.1.10909125'
|
||||
ndkVersion '26.3.11579264'
|
||||
|
||||
defaultConfig {
|
||||
// Default package name is taken from the manifest and should be app.organicmaps
|
||||
|
@ -187,6 +124,11 @@ android {
|
|||
def njobs = ''
|
||||
if (project.hasProperty('njobs')) njobs = project.getProperty('njobs')
|
||||
|
||||
def enableVulkanDiagnostics = 'OFF'
|
||||
if (project.hasProperty('enableVulkanDiagnostics')) {
|
||||
enableVulkanDiagnostics = project.getProperty('enableVulkanDiagnostics')
|
||||
}
|
||||
|
||||
cmake {
|
||||
cppFlags '-fexceptions', '-frtti'
|
||||
// There is no sense to enable sections without gcc's --gc-sections flag.
|
||||
|
@ -194,7 +136,7 @@ android {
|
|||
'-Wno-extern-c-compat'
|
||||
arguments '-DANDROID_TOOLCHAIN=clang', '-DANDROID_STL=c++_static',
|
||||
"-DOS=$osName", '-DSKIP_TESTS=ON', '-DSKIP_TOOLS=ON', "-DUSE_PCH=$pchFlag",
|
||||
"-DNJOBS=$njobs"
|
||||
"-DNJOBS=$njobs", "-DENABLE_VULKAN_DIAGNOSTICS=$enableVulkanDiagnostics"
|
||||
targets 'organicmaps'
|
||||
}
|
||||
}
|
||||
|
@ -241,6 +183,7 @@ android {
|
|||
|
||||
web {
|
||||
dimension 'default'
|
||||
applicationIdSuffix '.web'
|
||||
versionName = android.defaultConfig.versionName + '-Web'
|
||||
buildConfigField 'String', 'SUPPORT_MAIL', '"apk@organicmaps.app"'
|
||||
}
|
||||
|
@ -285,8 +228,6 @@ android {
|
|||
disable 'CustomSplashScreen'
|
||||
// https://github.com/organicmaps/organicmaps/issues/3610
|
||||
disable 'InsecureBaseConfiguration'
|
||||
// https://github.com/organicmaps/organicmaps/issues/3608
|
||||
disable 'UnusedResources'
|
||||
abortOnError true
|
||||
}
|
||||
|
||||
|
@ -322,8 +263,7 @@ android {
|
|||
jniDebuggable true // Enable jni debug build
|
||||
zipAlignEnabled true
|
||||
signingConfig signingConfigs.debug
|
||||
resValue 'string', 'app_id', android.defaultConfig.applicationId + applicationIdSuffix
|
||||
resValue 'string', 'app_name', project.ext.appName + ' ' + '(Debug)'
|
||||
resValue 'string', 'app_name', 'Debug Organic Maps'
|
||||
// Do not generate separate debug symbols for debug apps, because we don't distribute them.
|
||||
ndk.debugSymbolLevel = 'none'
|
||||
|
||||
|
@ -343,7 +283,6 @@ android {
|
|||
// Includes the default ProGuard rules files that are packaged with the Android Gradle plugin.
|
||||
// To learn more, go to the documentation section about R8 configuration files.
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
resValue 'string', 'app_id', android.defaultConfig.applicationId
|
||||
resValue 'string', 'app_name', project.ext.appName
|
||||
// Full size symbols are too big for Google, 217mb aab vs 95mb.
|
||||
ndk.debugSymbolLevel = 'symbol_table'
|
||||
|
@ -365,8 +304,7 @@ android {
|
|||
// To learn more, go to the documentation section about R8 configuration files.
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
matchingFallbacks = ['debug', 'release']
|
||||
resValue 'string', 'app_id', android.defaultConfig.applicationId + applicationIdSuffix
|
||||
resValue 'string', 'app_name', project.ext.appName + ' ' + '(Beta)'
|
||||
resValue 'string', 'app_name', 'Beta Organic Maps'
|
||||
// Full size symbols are too big for Google, 217mb aab vs 95mb.
|
||||
ndk.debugSymbolLevel = 'symbol_table'
|
||||
|
||||
|
@ -401,33 +339,80 @@ android {
|
|||
}
|
||||
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled true
|
||||
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
packagingOptions.jniLibs {
|
||||
excludes += [
|
||||
'lib/**/libVkLayer_khronos_validation.so',
|
||||
'lib/**/libVkLayer_core_validation.so',
|
||||
'lib/**/libVkLayer_threading.so',
|
||||
'lib/**/libVkLayer_image.so',
|
||||
'lib/**/libVkLayer_parameter_validation.so',
|
||||
'lib/**/libVkLayer_object_tracker.so',
|
||||
'lib/**/libVkLayer_swapchain.so',
|
||||
'lib/**/libVkLayer_unique_objects.so',
|
||||
]
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
|
||||
|
||||
// Google Play Location Services
|
||||
//
|
||||
// Please add symlinks to google/java/app/organicmaps/location for each new gms-enabled flavor below:
|
||||
// ```
|
||||
// mkdir -p src/$flavor/java/app/organicmaps/
|
||||
// ln -sf ../../../../google/java/app/organicmaps/location src/$flavor/java/app/organicmaps/
|
||||
// ls -la src/$flavor/java/app/organicmaps/location/GoogleFusedLocationProvider.java
|
||||
// ```
|
||||
//
|
||||
webImplementation 'com.google.android.gms:play-services-location:21.2.0'
|
||||
googleImplementation 'com.google.android.gms:play-services-location:21.2.0'
|
||||
huaweiImplementation 'com.google.android.gms:play-services-location:21.2.0'
|
||||
|
||||
// Google Firebase Services
|
||||
if (googleFirebaseServicesEnabled) {
|
||||
// Import the BoM for the Firebase platform
|
||||
implementation platform('com.google.firebase:firebase-bom:32.8.0')
|
||||
// Add the dependencies for the Crashlytics and Analytics libraries
|
||||
// When using the BoM, you don't specify versions in Firebase library dependencies
|
||||
implementation 'com.google.firebase:firebase-crashlytics'
|
||||
implementation 'com.google.firebase:firebase-crashlytics-ndk'
|
||||
}
|
||||
|
||||
// This line is added as a workaround for duplicate classes error caused by some outdated dependency:
|
||||
// > A failure occurred while executing com.android.build.gradle.internal.tasks.CheckDuplicatesRunnable
|
||||
// We don't use Kotlin, but some dependencies are actively using it.
|
||||
// See https://stackoverflow.com/a/75719642
|
||||
implementation 'androidx.core:core:1.12.0'
|
||||
implementation(platform('org.jetbrains.kotlin:kotlin-bom:1.9.23'))
|
||||
implementation 'androidx.annotation:annotation:1.7.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.car.app:app:1.7.0-alpha02'
|
||||
implementation 'androidx.car.app:app-projected:1.7.0-alpha02'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.fragment:fragment:1.6.2'
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||
implementation 'androidx.work:work-runtime:2.9.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-process:2.7.0'
|
||||
implementation 'com.google.android.material:material:1.11.0'
|
||||
// Fix for app/organicmaps/util/FileUploadWorker.java:14: error: cannot access ListenableFuture
|
||||
// https://github.com/organicmaps/organicmaps/issues/6106
|
||||
implementation 'com.google.guava:guava:33.1.0-android'
|
||||
implementation 'com.github.devnullorthrow:MPAndroidChart:3.2.0-alpha'
|
||||
implementation 'net.jcip:jcip-annotations:1.0'
|
||||
|
||||
// Test Dependencies
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.mockito:mockito-core:5.11.0'
|
||||
testImplementation 'org.mockito:mockito-inline:5.2.0'
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs << '-Xlint:unchecked' << '-Xlint:deprecation'
|
||||
}
|
||||
|
||||
android.buildTypes.all { buildType ->
|
||||
def suffix = applicationIdSuffix != null ? applicationIdSuffix : ""
|
||||
def authorityValue = android.defaultConfig.applicationId + suffix + ".provider"
|
||||
android.applicationVariants.all { variant ->
|
||||
def authorityValue = variant.applicationId + ".provider"
|
||||
def authority = "\"" + authorityValue + "\""
|
||||
buildConfigField 'String', 'FILE_PROVIDER_AUTHORITY', authority
|
||||
manifestPlaceholders += [FILE_PROVIDER_PLACEHOLDER : authorityValue]
|
||||
variant.buildConfigField 'String', 'FILE_PROVIDER_AUTHORITY', authority
|
||||
def flavor = variant.getMergedFlavor()
|
||||
flavor.manifestPlaceholders += [FILE_PROVIDER_PLACEHOLDER : authorityValue]
|
||||
variant.resValue 'string', 'app_id', variant.applicationId
|
||||
}
|
||||
|
||||
task prepareGoogleReleaseListing {
|
||||
|
@ -458,8 +443,7 @@ task prepareGoogleReleaseListing {
|
|||
|
||||
play {
|
||||
enabled.set(false)
|
||||
track.set('production')
|
||||
userFraction.set(Double.valueOf(0.10)) // 10%
|
||||
track.set('alpha')
|
||||
defaultToAppBundles.set(true)
|
||||
releaseStatus.set(ReleaseStatus.IN_PROGRESS)
|
||||
serviceAccountCredentials.set(file('google-play.json'))
|
||||
|
@ -471,7 +455,7 @@ huaweiPublish {
|
|||
credentialsPath = "$projectDir/huawei-appgallery.json"
|
||||
buildFormat = 'aab'
|
||||
deployType = 'draft' // confirm manually
|
||||
releaseNotes = []
|
||||
def releaseDescriptions = []
|
||||
def localeOverride = [
|
||||
'am' : 'am-ET',
|
||||
'gu': 'gu_IN',
|
||||
|
@ -489,8 +473,9 @@ huaweiPublish {
|
|||
def path = file.getPath()
|
||||
def locale = file.parentFile.getName()
|
||||
locale = localeOverride.get(locale, locale)
|
||||
releaseNotes.add(new ru.cian.huawei.publish.ReleaseNote(locale, path))
|
||||
releaseDescriptions.add(new ru.cian.huawei.publish.ReleaseNote(locale, path))
|
||||
}
|
||||
releaseNotes = new ru.cian.huawei.publish.ReleaseNotesExtension(releaseDescriptions, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
• Android Auto
|
||||
• Выпраўлена праблема з перыядычным выключэннем экрана
|
||||
• Дадзеныя карты OpenStreetMap па стане на 5 студзеня
|
||||
• Палепшаныя лыжныя трасы і стыль для актыўнага адпачынку
|
||||
• Дададзена максімальная ёмістасць для некаторых месцаў
|
||||
• Дададзены азербайджанскія пераклады
|
||||
• Сартаванне закладак і сцежак па імені
|
||||
• Дадзеныя OpenStreetMap па стане на 29 красавіка
|
||||
• Трэба ізноў залагініцца ў OSM
|
||||
• Складаныя паходныя сцежкі зараз карычневыя, а экспертныя шляхі чорныя
|
||||
• Веласіпедныя дарожкі цяпер цёмна-сінія, пункцірам там, дзе яны ідуць па пешаходных сцежках
|
||||
• Цяпер бачна дарогі пад напаўпразрыстымі мастамі
|
||||
• Выпраўлены імпарт GPX з прыкладання Google Files
|
||||
|
||||
…падрабязней на omaps.org/news
|
||||
… больш падрабязна на omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
• Android Auto
|
||||
• S'ha corregit un problema amb la pantalla que es desengegava intermitentment
|
||||
• Dades cartogràfiques de l'OpenStreetMap al 5 de gener
|
||||
• Millores en l'estil «Senderisme» i les pistes d'esquí
|
||||
• Traducció a l'àzeri
|
||||
• Correcció dels paràmetres de veu en castellà i portuguès
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,10 +1,53 @@
|
|||
Organic Maps je navigační aplikace s ochranou soukromí pro řidiče, turisty a cyklisty. Aplikace má navigaci s ochranou soukromí - žádné sledování polohy, neshromažďuje data a nemá reklamy. Vyhledávání, směrování a navigace fungují bez mobilního signálu, což je ideální pro cestování na vzdálených turistických trasách nebo místech se špatným připojením. Aplikace Organic Maps využívá data OpenStreetMap, na jejichž tvorbě se podílejí davy přispěvatelů z celého světa. Projekt je řízen komunitou, kód je open-source a upřednostňuje komunitní rozvoj a spolupráci.
|
||||
‣ Naše bezplatná aplikace vás nesleduje, neobsahuje reklamy a potřebuje vaši podporu.
|
||||
‣ Je neustále vylepšována přispěvateli a naším malým týmem v našem volném čase.
|
||||
‣ Pokud je na mapě něco špatně nebo na ní něco chybí, opravte to prosím na <b>OpenStreetMap</b>. Své změny uvidíte v pozdějších aktualizacích map.
|
||||
‣ Pokud nefunguje navigace nebo vyhledávání, zkontrolujte to prosím nejprve na osm.org a až poté nám napište e-mail. Odpovídáme na <i>KAŽDÝ</i> e-mail a problémy opravujeme jak nejdříve můžeme.
|
||||
|
||||
• Není nutný mobilní signál - vyhledávání a trasování bez signálu
|
||||
• Efektivní využití baterie - menší vybíjení baterie
|
||||
• Rychlé vyhledávání - rychlé vyhledávání míst
|
||||
<b>Vaše zpětná vazba a 5-hvězdičková hodnocení jsou pro nás nejlepší motivací!</b>
|
||||
|
||||
Klíčové funkce:
|
||||
|
||||
• Bezplatné, open-source, bez reklam, bez sledování
|
||||
• Podrobné offline mapy s místy, které neexistují v Mapách Google, díky komunitě <b>OpenStreetMap</b>
|
||||
• Cyklotrasy, turistické trasy a pěší stezky
|
||||
• Vrstevnice, výškové profily, vrcholy a svahy
|
||||
• Pěší, cyklistická a automobilová navigace krok po kroku s hlasovými pokyny a Android Auto
|
||||
• Rychlý offline vyhledávač
|
||||
• Export a import záložek a tras ve formátech KML, KMZ a GPX
|
||||
• Tmavý režim pro ochranu vašich očí
|
||||
|
||||
Organic Maps <i>zatím</i> neobsahují cool vychytávky jako veřejnou dopravu nebo satelitní mapy, <i>s vaší pomocí a podporou</i> ale můžeme postupně mapy vylepšovat.
|
||||
|
||||
Aplikace Organic Maps je <b>čistá a organická, vytvořená s láskou</b>:
|
||||
|
||||
• Neskutečně rychlý offline zážitek
|
||||
• Respektuje vaše soukromí
|
||||
• Šetří vaší baterii
|
||||
• Žádné neočekávané poplatky za mobilní data
|
||||
• Je jednoduchá na používání, obsahuje pouze nejdůležitější funkce
|
||||
|
||||
Neobsahuje sledovací prvky a další špatnosti:
|
||||
|
||||
V Organic Maps si ceníme soukromí:
|
||||
• Žádné sledování polohy
|
||||
• Žádné shromažďování dat
|
||||
• Žádné reklamy
|
||||
• Žádné sledování
|
||||
• Žádné sbírání dat
|
||||
• Žádné volání domů
|
||||
• Žádná otravná registrace
|
||||
• Žádné povinné tutoriály
|
||||
• Žádný e-mailový spam
|
||||
• Žádná vyskakovací oznámení
|
||||
• Žádný crapware
|
||||
• Ž̶á̶d̶n̶é̶ ̶p̶e̶s̶t̶i̶c̶i̶d̶y̶ Plně organické
|
||||
|
||||
V Organic Maps věříme, že <b>soukromí je základní lidské právo</b>:
|
||||
|
||||
• Organic Maps jsou nezávislý komunitní open-source projekt
|
||||
• Chráníme vaše soukromí před zvědavýma očima Big Tech firem
|
||||
• Nezávisle na vašem umístění jste v bezpečí
|
||||
|
||||
Podle hlášení Exodus Privacy neobsahuje aplikace žádné sledovací prvky a vyžaduje pouze minimální množství oprávnění.
|
||||
|
||||
Pro další podrobnosti a často kladené dotazy prosím navštivte webové stránky <b><i>organicmaps.app</i></b>. Kontaktovat nás můžete přes @OrganicMapsApp na Telegramu.
|
||||
|
||||
Odmítněte sledování - přijměte svou svobodu.
|
||||
<b>Vyzkoušejte Organic Maps!</b>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,10 +1,53 @@
|
|||
Organic Maps es una aplicación de navegación GPS con privacidad, para conductores, excursionistas y ciclistas. La aplicación ofrece navegación con privacidad: sin seguimiento en la ubicación, sin recopilación de los datos y sin anuncios. La búsqueda, el enrutamiento y la navegación funcionan sin la señal del teléfono móvil, lo que resulta ideal para viajar por rutas de senderismo lejanas o lugares con malas conexiones. Organic Maps utiliza los datos de OpenStreetMap, de origen colectivo, con colaboradores de todo el mundo. El proyecto está impulsado por la comunidad, el código es abierto y da prioridad al desarrollo y la colaboración de la comunidad.
|
||||
‣ Nuestra aplicación gratuita no te rastrea, no tiene anuncios y necesita tu apoyo.
|
||||
‣ Está siendo mejorado constantemente por los colaboradores y nuestro pequeño equipo, en nuestro tiempo libre.
|
||||
‣ Si algo está mal o falta en el mapa, corríjalo en <b>OpenStreetMap</b> y vea sus cambios en la futura actualización de mapas.
|
||||
‣ Si la navegación o la búsqueda no funcionan, compruébelo primero en osm.org y luego envíenos un correo electrónico. ¡Respondemos a <i>TODOS los</i> correos electrónicos y lo solucionaremos lo antes posible!
|
||||
|
||||
• Sin necesidad de señal del móvil: busca y rastrea sin necesidad de la señal.
|
||||
• Utiliza de forma eficiente la batería - Reduce el consumo de batería
|
||||
• Búsqueda rápida - Encuentra ubicaciones rápidamente
|
||||
<b>¡Sus comentarios y reseñas de 5 estrellas son los mejores motivadores para nosotros!</b>
|
||||
|
||||
Características principales:
|
||||
|
||||
• Gratis, de código abierto, sin anuncios, sin seguimiento
|
||||
• Mapas detallados sin conexión con lugares que no existen en Google Maps, gracias a la comunidad <b>OpenStreetMap</b>
|
||||
• Rutas en bicicleta, rutas de senderismo y senderos
|
||||
• Curvas de nivel, perfiles de elevación, picos y pendientes
|
||||
• Navegación paso a paso a pie, en bicicleta y en coche con guía de voz y Android Auto
|
||||
• Búsqueda rápida sin conexión
|
||||
• Exportación e importación de marcadores y pistas en formatos KML, KMZ, GPX
|
||||
• Modo oscuro para proteger tus ojos
|
||||
|
||||
<i>Todavía</i> no hay transporte público, mapas satelitales y otras características interesantes en Organic Maps. Pero con <i>su ayuda y apoyo</i>, podemos hacer mejores mapas paso a paso.
|
||||
|
||||
Organic Maps es <b>puro y orgánico, hecho con amor</b>:
|
||||
|
||||
• Experiencia offline ultrarrápida
|
||||
• Respeta tu privacidad
|
||||
• Ahorra batería
|
||||
• Sin cargos inesperados por datos móviles
|
||||
• Fácil de usar, con sólo las características más importantes incluidas
|
||||
|
||||
Libre de rastreadores y otras cosas malas:
|
||||
|
||||
En Organic Maps, valoramos la privacidad:
|
||||
• No rastreamos la ubicación
|
||||
• Sin recopilación de datos
|
||||
• Sin anuncios
|
||||
• Sin seguimiento
|
||||
• Ausencia de recopilación de datos
|
||||
• No hay llamadas a casa
|
||||
• Sin registro molesto
|
||||
• No hay tutoriales obligatorios
|
||||
• No hay spam de correo electrónico ruidoso
|
||||
• No hay notificaciones push
|
||||
• Sin crapware
|
||||
• N̶o̶p̶e̶s̶t̶i̶i̶i̶i̶e̶s̶s̶ puramente orgánico
|
||||
|
||||
En Organic Maps, creemos que <b>la privacidad es un derecho humano fundamental</b>:
|
||||
|
||||
• Organic Maps es un proyecto independiente de código abierto impulsado por la comunidad
|
||||
• Protegemos la privacidad de las miradas indiscretas de las grandes tecnológicas
|
||||
• Mantente seguro estés donde estés
|
||||
|
||||
De acuerdo con el Informe de privacidad de Exodus, no se encuentran rastreadores y solo se requieren permisos mínimos.
|
||||
|
||||
Visite <b><i>organicmaps.app</i></b> sitio web para obtener más detalles y preguntas frecuentes, y contáctenos directamente en @OrganicMapsApp en Telegram.
|
||||
|
||||
Rechaza la vigilancia, abraza tu libertad.
|
||||
<b>¡Dale una oportunidad a Organic Maps!</b>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
• Android Auto
|
||||
• Se resolvió un problema con la pantalla que se apagaba intermitentemente
|
||||
• Datos cartográficos de OpenStreetMap al 5 de enero
|
||||
• Mejoras en el estilo «Senderismo» y las pistas de esquí
|
||||
• Traducción al azerí
|
||||
• Corrección de las configuraciones de voz en español y portugués
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
Para conocer más, vaya a omaps.org/es/news
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1 +1 @@
|
|||
Kogukonna loodud kaardid reisijatele, turistidele, ratturitele ja matkajatele
|
||||
Privaatne, kogukonna poolt juhitud ja avatud lähtekoodiga kaardirakendus
|
||||
|
|
|
@ -1 +1 @@
|
|||
Organic Maps: gps kaardid
|
||||
Kaardirakendus Organic Maps
|
||||
|
|
|
@ -1 +1 @@
|
|||
Organic Maps Offline Hike, Bike, GPS Navigation
|
||||
Organic Maps: teejuht, mis ei vaja võrguühendust
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,10 +1,53 @@
|
|||
Organic Maps est une application de navigation GPS respectueuse de la vie privée, destinée aux conducteurs, aux randonneurs et aux cyclistes. L'application permet de naviguer en toute confidentialité - pas de suivi de la localisation, pas de collecte de données et pas de publicité. La recherche, le routage et la navigation fonctionnent même sans signal cellulaire, ce qui est idéal pour les voyages sur des sentiers de randonnée éloignés ou dans des endroits où les connexions sont insuffisantes. Organic Maps utilise les données d'OpenStreetMap, dont les contributeurs viennent du monde entier. Le projet est porté par la communauté, le code est ouvert et la priorité est donnée au développement de la communauté et à la collaboration.
|
||||
‣ Notre application gratuite ne vous trace pas, n'a pas de publicité et a besoin de votre soutien.
|
||||
‣ Elle est constamment améliorée par les contributeurs et notre petite équipe, sur notre temps libre.
|
||||
‣ Si quelque chose manque ou est erroné sur la carte, nous vous invitons à le corriger dans <b>OpenStreetMap</b> et à voir vos changements apparaître dans les mises à jour des cartes.
|
||||
‣ Si la navigation ou la recherche ne fonctionne pas, vérifiez d'abord sur osm.org, puis envoyez-nous un courriel. Nous répondons à <i>TOUS</i> les courriels et nous les réparons le plus rapidement possible !
|
||||
|
||||
- Aucun signal cellulaire requis - Recherche et itinéraire sans signal
|
||||
- Utilisation optimale de la batterie - Réduction de la consommation de batterie
|
||||
- Recherche rapide - Trouver des lieux rapidement
|
||||
<b>Vos commentaires et vos évaluations 5 étoiles sont les meilleures motivations pour nous!</b>
|
||||
|
||||
Principales fonctionnalités :
|
||||
|
||||
- Gratuit, open-source, pas de publicité, pas de traçage
|
||||
- Cartes hors ligne détaillées avec des lieux qui n'existent pas sur les cartes Google, grâce à la communauté <b>OpenStreetMap</b>.
|
||||
- Itinéraires cyclistes, sentiers de randonnée et chemins de promenade
|
||||
- Courbes de niveau, profils d'altitude, sommets et pentes
|
||||
- Navigation à pied, à vélo et en voiture, virage par virage, avec guidage vocal et Android Auto
|
||||
- Recherche rapide hors ligne
|
||||
- Exportation et importation de marque-pages et de tracés aux formats KML, KMZ et GPX
|
||||
- Mode sombre pour protéger vos yeux
|
||||
|
||||
Organic Maps ne propose pas encore de transports publics, de cartes satellites et d'autres fonctionnalités intéressantes. Mais avec <i>votre aide et votre soutien</i>, nous pouvons améliorer les cartes étape par étape.
|
||||
|
||||
Organic Maps est <b>pur et organique, fait avec amour</b> :
|
||||
|
||||
- Une expérience hors ligne ultra-rapide
|
||||
- Respecte votre vie privée
|
||||
- Économise votre batterie
|
||||
- Sans frais de données mobiles inattendus
|
||||
- Simple à utiliser, avec seulement les fonctions les plus importantes incluses
|
||||
|
||||
Exempt de trackers et d'autres éléments nuisibles :
|
||||
|
||||
Chez Organic Maps, nous accordons une grande importance à la confidentialité :
|
||||
- Pas de géolocalisation
|
||||
- Pas de collecte de données
|
||||
- Pas de publicité
|
||||
- Pas de traçage
|
||||
- Pas de collecte de données
|
||||
- Pas d'envoi de donnée clandestin
|
||||
- Pas d'inscriptions inutiles
|
||||
- Pas de didacticiel contraignant
|
||||
- Pas de spam bruyant
|
||||
- Pas de notifications push
|
||||
- Pas de logiciels malveillants
|
||||
- N̶o̶ ̶p̶e̶s̶t̶i̶c̶i̶d̶e̶s̶ Purement organique
|
||||
|
||||
Chez Organic Maps, nous pensons que <b>la vie privée est un droit humain fondamental</b> :
|
||||
|
||||
- Organic Maps est un projet open-source mené par une communauté indépendante.
|
||||
- Nous protégeons la vie privée des regards indiscrets des grandes entreprises.
|
||||
- Restez en sécurité où que vous soyez
|
||||
|
||||
Selon le rapport de confidentialité d'Exodus, il n'y a aucun traceur et les autorisations requises sont minimales.
|
||||
|
||||
Veuillez visiter le site Web <b><i>organicmaps.app</i></b> pour plus de détails et une FAQ, et contactez-nous directement à @OrganicMapsApp sur Telegram.
|
||||
|
||||
Rejetez la surveillance - embrassez votre liberté.
|
||||
<b>Donnez une chance à Organic Maps!</b>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
• Android Auto
|
||||
• Fixed issue with the screen intermittently turning off
|
||||
• OpenStreetMap map data as of January 5
|
||||
• Improved skiing pistes and Outdoors style
|
||||
• Added Max Capacity for places
|
||||
• Added Azerbaijani translations
|
||||
• Fixed Spanish and Portugal voice settings
|
||||
• Sort bookmarks and tracks by name
|
||||
• Transitioned to the new OpenStreetMap authentication mechanism — all authenticated OSM users must re-login
|
||||
• OpenStreetMap data as of April 29
|
||||
• Difficult hiking trails are now brown-dotted, expert ones are black
|
||||
• Dark blue lines for dedicated cycleways, mixed with white/brown dashes for shared foot/cycle paths
|
||||
• Roads are now visible under semi-transparent bridges
|
||||
• Fixed GPX import from Google Files app
|
||||
|
||||
…more details at omaps.org/news
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue