mirror of
https://codeberg.org/comaps/comaps
synced 2026-01-07 21:13:55 +00:00
Compare commits
59 Commits
test/2025.
...
pastk-gen-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8e2eb037f | ||
|
|
7591e99cef | ||
|
|
8584d6634e | ||
|
|
b8b529ca78 | ||
|
|
486babe27e | ||
|
|
fedbc2d57f | ||
|
|
2e7713568a | ||
|
|
dfaac372ee | ||
|
|
15fd7a0bd5 | ||
|
|
467e97b231 | ||
|
|
2075b79dee | ||
|
|
55d162f354 | ||
|
|
2fa1714575 | ||
|
|
9a2dc676a7 | ||
|
|
4bcf5a8f98 | ||
|
|
6afa6a8476 | ||
|
|
b04bda4ff2 | ||
|
|
da150c87a5 | ||
|
|
e17e122f0b | ||
|
|
96d9cbf1fa | ||
|
|
cf2fea216e | ||
|
|
2c9a85d5a3 | ||
|
|
42c30cb775 | ||
|
|
c58f915063 | ||
|
|
d754bf0e0a | ||
|
|
622a5bf3bf | ||
|
|
cecb9d9200 | ||
|
|
f731ff0612 | ||
|
|
2524d074b4 | ||
|
|
d56432e484 | ||
|
|
04b3c01816 | ||
|
|
8dc633bfd9 | ||
|
|
23f8a73ef6 | ||
|
|
099ecde058 | ||
|
|
6d6140ee33 | ||
|
|
623c4ed9fe | ||
|
|
b4cc6b140b | ||
|
|
fca2a64a80 | ||
|
|
81c06f1ae7 | ||
|
|
2bfcf0089b | ||
|
|
0d08a53224 | ||
|
|
980bd36d02 | ||
|
|
e16c8f3591 | ||
|
|
727c0db4b0 | ||
|
|
3b44a61f9d | ||
|
|
3195450519 | ||
|
|
58f7c24620 | ||
|
|
bcf65ee2e5 | ||
|
|
fc859c65d5 | ||
|
|
794f0f01aa | ||
|
|
032183414b | ||
|
|
aab883171d | ||
|
|
9d31c7f37f | ||
|
|
1f950d2c0b | ||
|
|
111bb0b94c | ||
|
|
5567e011db | ||
|
|
6cafc1da75 | ||
|
|
5eff4f56ca | ||
|
|
d38ffe2fa8 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,6 +9,7 @@ Makefile.Release
|
||||
object_script.*.Debug
|
||||
object_script.*.Release
|
||||
compile_commands.json
|
||||
*.local.*
|
||||
|
||||
stxxl.errlog
|
||||
stxxl.log
|
||||
|
||||
Submodule 3party/CMake-MetalShaderSupport updated: 84209c32e5...989857d2e5
@@ -31,6 +31,7 @@ if (NOT WITH_SYSTEM_PROVIDED_3PARTY)
|
||||
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.
|
||||
set(GFLAGS_BUILD_TESTING OFF)
|
||||
|
||||
Submodule 3party/gflags updated: 52e94563eb...a738fdf933
Submodule 3party/jansson/jansson updated: 96d160df90...61fc3d0e28
@@ -94,6 +94,11 @@ if (PLATFORM_WIN)
|
||||
)
|
||||
endif()
|
||||
|
||||
# Try fast native arch.
|
||||
if (PLATFORM_LINUX)
|
||||
add_compile_options(-march=native)
|
||||
endif()
|
||||
|
||||
# Built-in CMake configurations: Debug, Release, RelWithDebInfo, MinSizeRel
|
||||
if (${CMAKE_BUILD_TYPE} STREQUAL "Debug")
|
||||
add_definitions(-DDEBUG)
|
||||
@@ -103,12 +108,29 @@ if (${CMAKE_BUILD_TYPE} STREQUAL "Debug")
|
||||
elseif (${CMAKE_BUILD_TYPE} MATCHES "Rel")
|
||||
add_definitions(-DRELEASE)
|
||||
if (NOT MSVC)
|
||||
add_compile_options(-Ofast) # Also enables -ffast-math
|
||||
add_compile_options(-Ofast $<$<CXX_COMPILER_ID:GNU>:-flto=auto>) # Also enables -ffast-math
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR "Unknown build type: " ${CMAKE_BUILD_TYPE})
|
||||
endif()
|
||||
|
||||
if (${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo")
|
||||
add_compile_options(-fno-omit-frame-pointer)
|
||||
endif()
|
||||
|
||||
# Linux GCC LTO plugin fix.
|
||||
if (PLATFORM_LINUX AND (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND (CMAKE_BUILD_TYPE MATCHES "^Rel"))
|
||||
# To force errors if LTO was not enabled.
|
||||
add_compile_options(-fno-fat-lto-objects)
|
||||
# To fix ar and ranlib "plugin needed to handle lto object".
|
||||
string(REGEX MATCH "[0-9]+" GCC_MAJOR_VERSION ${CMAKE_CXX_COMPILER_VERSION})
|
||||
file(GLOB_RECURSE plugin /usr/lib/gcc/*/${GCC_MAJOR_VERSION}/liblto_plugin.so)
|
||||
set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> --plugin ${plugin} qcs <TARGET> <OBJECTS>")
|
||||
set(CMAKE_C_ARCHIVE_FINISH "<CMAKE_RANLIB> --plugin ${plugin} <TARGET>")
|
||||
set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> --plugin ${plugin} qcs <TARGET> <OBJECTS>")
|
||||
set(CMAKE_CXX_ARCHIVE_FINISH "<CMAKE_RANLIB> --plugin ${plugin} <TARGET>")
|
||||
endif()
|
||||
|
||||
message(STATUS "Build type: " ${CMAKE_BUILD_TYPE})
|
||||
|
||||
if (PLATFORM_LINUX OR PLATFORM_ANDROID)
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
Erster CoMaps Release!
|
||||
Was ist neu im Vergleich zu Organic Maps:
|
||||
• OpenStreetMap-Daten vom 15. Mai
|
||||
• OSM Login jetzt über den Webbrowser
|
||||
• Der halbtransparente Hintergrund in der U-Bahn-Ansicht wurde entfernt
|
||||
• Größe der lokalen Karte beim ersten Karten download anzeigen
|
||||
• Der Metaserver wurde durch eine feste Liste an Kartenservern ersetzt
|
||||
• Der Nachtmodus schaltet jetzt besser um, auch wenn kein GPS-Signal da ist
|
||||
• OpenStreetMap Daten vom 2. Juni
|
||||
• Neue Einstellungsoption zum Ändern oder Ausblenden der "Über CoMaps" Schaltfläche ganz links
|
||||
• Routen als GPS Track speichern
|
||||
• Qingdao Metro, Gärtnereien, Leitplanken, Leitern, Studios, Tanzsäle, Feuerstellen und Stundenhotels hinzugefügt
|
||||
• transparente Navigationsleiste im Light Mode
|
||||
• Mastodon und Bluesky Kontaktoptionen für POIs und im OSM-Editor hinzugefügt
|
||||
• Anzeige der Kompassgradzahl in der Richtungspfeilansicht
|
||||
• Übersetzungen aktualisiert
|
||||
• Rebranding – das Logo ist noch vorläufig
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
Initial CoMaps release!
|
||||
Changes from the Organic Maps upstream:
|
||||
• OpenStreetMap data as of May 15
|
||||
• login to OSM account via a web browser
|
||||
• removed semi-transparent background in Subway layer
|
||||
• replaced metaserver with a static map servers list
|
||||
• add local map size to the startup download screen
|
||||
• improved auto night mode switch when there is no location fix
|
||||
• fix incorrect map info when tapping on a not downloaded region
|
||||
• OpenStreetMap data as of June 2
|
||||
• add a setting to change the leftmost button or hide it
|
||||
• save built routes as tracks
|
||||
• add Qingdao metro, plant nurseries, highway guard rails, ladders, studios, dance venues, firepits, love hotels
|
||||
• transparent system navigation bar in the light mode
|
||||
• add Mastodon and Bluesky contact options to POIs and OSM editor
|
||||
• display Azimuth angle in direction arrow view
|
||||
• update translations
|
||||
• rebranding - the logo is provisional still
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
Lanzamiento inicial de CoMaps
|
||||
|
||||
Cambios respecto a la versión de Organic Maps:
|
||||
• Datos nuevos al 15 de mayo
|
||||
• Inicio de sesión a OSM a través de navegador
|
||||
• Eliminado fondo semitransparente en la capa de Metro
|
||||
• Sustitución del metaserver por lista estática de servidores
|
||||
• Añadido tamaño del mapa local al inicio
|
||||
• Mejora del cambio automático al modo nocturno al no conocerse ubicación
|
||||
• Correcciones de fallos
|
||||
• Actualización de traducciones
|
||||
• Cambio de branding con logo provisional
|
||||
@@ -0,0 +1 @@
|
||||
Helppo karttanavigointi - Löydä lisää matkaltasi - Yhteisön voimin
|
||||
@@ -1 +1 @@
|
||||
CoMaps - Vaella, pyöräile, autoile ilman verkkoyhteyttä yksityisesti
|
||||
CoMaps - Patikoi, pyöräile, autoile ilman verkkoyhteyttä yksityisesti
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
Première version de CoMaps!
|
||||
Changements par rapport à Organic Maps:
|
||||
• Données OpenStreetMap du 15 mai
|
||||
• Connexion au compte OSM via le navigateur
|
||||
• Retrait du fond semi-transparent sur la couche Métro
|
||||
• Remplacement du metaserveur par une liste statique de serveurs
|
||||
• Ajout de la taille de la carte locale au premier démarrage
|
||||
• Amélioration du mode nuit automatique
|
||||
• Correction de la suggestion de téléchargements de cartes
|
||||
• Données OpenStreetMap du 02 juin
|
||||
• Ajout d'une option pour personnaliser le bouton tout à gauche sur l'écran principal
|
||||
• Ajout de la possibilité d'enregistrer un itinéraire en tant que traces GPS
|
||||
• Ajout du métro de Qingdao, et divers objets sur la carte
|
||||
• Support de la barre de navigation transparent en mode clair
|
||||
• Ajout des tags Mastodon et Bluesky sur les lieux et dans l'éditeur
|
||||
• Affichage de l'azimut
|
||||
• Mise à jour des traductions
|
||||
• Rebranding - le logo est encore provisoire
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
• Data di OpenStreetmap fino a giugno 2°
|
||||
• Nuova impostazione per cambiare o modificare la positione del tasto sinistra
|
||||
• Salvare i percorsi costruiti come tracce
|
||||
• È stato aggiunto: metropolitana di Qingdao, giardinaggi, guardrail, scale, studio,
|
||||
sala da ballo, focolari, love hotel
|
||||
• Barra di navigatione transparente di sisteme in moda luce
|
||||
• Aggiungi le opzioni di contatto Mastodon e Bluesky ai POI e all'editor di OSM
|
||||
• Visualizza l'angolo di azimut nella freccia di direzione
|
||||
@@ -1,11 +0,0 @@
|
||||
Pierwsze wydanie CoMaps!
|
||||
Zmiany w stosunku do Organic Maps:
|
||||
• dane OSM z 15.05,
|
||||
• logowanie do OSM przez przeglądarkę,
|
||||
• usunięto półprzezroczyste tło w warstwie Metra,
|
||||
• zastąpiono metaserver statyczną listą serwerów map,
|
||||
• dodano rozmiar mapy do ekranu pobierania przy uruchamianiu,
|
||||
• poprawiono przełączanie trybu nocnego bez lokalizacji,
|
||||
• poprawiono niepoprawne informacje o mapie po dotknięciu niepobranego regionu,
|
||||
• zaktualizowano tłumaczenia,
|
||||
• rebranding - logo wciąż tymczasowe.
|
||||
@@ -1,9 +1,8 @@
|
||||
Lançamento inicial do CoMaps!
|
||||
Alterações em relação ao Organic Maps:
|
||||
• Dados do OSM de 15/05
|
||||
• Login na conta OSM por meio de autenticação no navegador
|
||||
• Remoção do fundo semitransparente na camada Metrô
|
||||
• Substituição do metaserver por uma lista estática de servidores de mapas
|
||||
• Melhoria na alternância automática do modo noturno quando não há localização
|
||||
• Dados do OSM de 2/06
|
||||
• Adicionada uma configuração para alterar ou ocultar o botão mais à esquerda
|
||||
• Salve rotas construídas como trilhas
|
||||
• Adicionado metrô de Qingdao, viveiros de plantas, guarda-corpos de rodovias, escadas, estúdios, casas de dança, fogueiras e motéis
|
||||
• Barra de navegação do sistema transparente no modo claro
|
||||
• Adicionadas opções de contato Mastodon e Bluesky aos POIs e ao editor OSM
|
||||
• Exibição de ângulo de azimute na visualização de seta de direção
|
||||
• Novas traduções
|
||||
• Rebranding - O logo ainda é provisório
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
Lançamento inicial do CoMaps!
|
||||
Alterações em relação ao Organic Maps:
|
||||
• Dados do OSM de 15/05
|
||||
• Login na conta OSM através de um navegador web auth
|
||||
• Removido o fundo semi-transparente na camada do Metro
|
||||
• Substituído o metaserver por uma lista estática de servidores de mapas
|
||||
• Melhoria na mudança automática do modo noturno quando não há localização
|
||||
• Novas traduções
|
||||
• Rebranding - logótipo ainda provisório
|
||||
@@ -0,0 +1,7 @@
|
||||
• карты OpenStreetMap от 2 июня
|
||||
• настройка для изменения функции левой кнопки или её скрытия
|
||||
• сохранение построенных маршрутов в виде треков
|
||||
• добавлены: метро в Qingdao, питомники растений, отбойники на шоссе, постоянные лестницы-стремянки, студии, места для танцев, кострища, отели любви
|
||||
• прозрачная полоска с системными кнопками (в светлом режиме)
|
||||
• в объекты на карте (а также в их редактор) добавлены Mastodon и Bluesky контакты
|
||||
• к стрелке направления на выбранный объект добавлен азимут
|
||||
@@ -0,0 +1 @@
|
||||
Helppo karttanavigointi - Löydä lisää matkaltasi - Yhteisön voimin
|
||||
@@ -0,0 +1,45 @@
|
||||
Um aplicativo de mapas gratuito e de código aberto, liderado pela comunidade e baseado em dados do OpenStreetMap, reforçado pelo compromisso com a transparência, privacidade e sem fins lucrativos.
|
||||
|
||||
Junte-se à comunidade e ajude a criar o melhor aplicativo de mapas.
|
||||
|
||||
• Use o aplicativo e divulgue-o.
|
||||
• Envie feedback e relate problemas.
|
||||
• Atualize os dados do mapa no aplicativo ou no site do OpenStreetMap.
|
||||
|
||||
<i>Seu feedback e avaliações 5 estrelas são o nosso melhor suporte!</i>
|
||||
|
||||
‣ <b>Simples e refinado</b>: recursos essenciais e fáceis de usar que simplesmente funcionam.
|
||||
‣ <b>Foco offline</b>: Planeje e navegue em sua viagem ao exterior sem a necessidade de sinal de celular, pesquise pontos de referência durante uma caminhada distante, etc. Todas as funções do aplicativo foram projetadas para funcionar offline.
|
||||
‣ <b>Respeitando a privacidade</b>: O aplicativo foi projetado com a privacidade em mente - não identifica pessoas, não rastreia e não coleta informações pessoais. Livre de anúncios.
|
||||
‣ <b>Economiza bateria e espaço</b>: Não esgota a bateria como outros aplicativos de navegação. Mapas compactos economizam espaço precioso no seu celular.
|
||||
‣ <b>Gratuito e desenvolvido pela comunidade</b>: Pessoas como você ajudaram a desenvolver o aplicativo adicionando locais ao OpenStreetMap, testando e dando feedback sobre os recursos, além de contribuir com suas habilidades de desenvolvimento e recursos financeiros.
|
||||
‣ <b>Tomada de decisões e finanças abertas e transparentes, sem fins lucrativos e totalmente de código aberto.</b>
|
||||
|
||||
‣ Simples e sofisticado: recursos essenciais e fáceis de usar que simplesmente funcionam.
|
||||
‣ Foco offline: Planeje e navegue em sua viagem ao exterior sem a necessidade de sinal de celular, pesquise pontos de referência durante uma caminhada distante, etc. Todas as funções do aplicativo foram projetadas para funcionar offline.
|
||||
‣ Respeito à privacidade: O aplicativo foi projetado com a privacidade em mente - não identifica pessoas, não rastreia e não coleta informações pessoais. Livre de anúncios.
|
||||
‣ Economiza bateria e espaço: Não consome bateria como outros aplicativos de navegação. Mapas compactos economizam espaço precioso no seu celular.
|
||||
‣ Gratuito e desenvolvido pela comunidade: Pessoas como você ajudaram a desenvolver o aplicativo adicionando lugares ao OpenStreetMap, testando e dando feedback sobre os recursos e contribuindo com dinheiro e habilidades de desenvolvimento.
|
||||
‣ Tomada de decisões e finanças abertas e transparentes, sem fins lucrativos e totalmente de código aberto.
|
||||
|
||||
Principais recursos:
|
||||
• Mapas detalhados para download com locais que não estão disponíveis em muitos mapas comerciais
|
||||
• Modo ao ar livre com trilhas destacadas, acampamentos, fontes de água, picos, curvas de nível, etc.
|
||||
• Trilhas para caminhada e ciclovias
|
||||
• Pontos de interesse como restaurantes, postos de gasolina, hotéis, lojas, pontos turísticos e muito mais
|
||||
• Pesquise por nome, endereço ou categoria de ponto de interesse
|
||||
• Navegação com anúncios de voz para caminhada, ciclismo ou direção
|
||||
• Marque seus lugares favoritos com um único toque
|
||||
• Sincronização com iCloud para seus favoritos e trilhas
|
||||
• Artigos offline da Wikipédia
|
||||
• Camada e direções de transporte público de metrô
|
||||
• Gravação de trilhas
|
||||
• Exporte e importe favoritos e trilhas nos formatos KML, KMZ e GPX
|
||||
• Modo escuro para usar à noite
|
||||
• Aprimore os dados do mapa para todos usando um editor básico integrado
|
||||
• Suporte para Android Auto
|
||||
|
||||
Por favor, reporte problemas com o aplicativo, sugira ideias e junte-se à nossa comunidade no site <b><i>comaps.app</i></b>.
|
||||
|
||||
<b>A Liberdade Chegou</b>
|
||||
Descubra sua jornada, navegue pelo mundo com privacidade e comunidade em primeiro lugar!
|
||||
@@ -0,0 +1 @@
|
||||
Fácil navegação - Descubra mais sobre sua jornada - Desenvolvido pela comunidade
|
||||
1
android/app/src/google/play/listings/pt-BR/title.txt
Normal file
1
android/app/src/google/play/listings/pt-BR/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
CoMaps - Navegue privadamente
|
||||
@@ -36,6 +36,7 @@ import androidx.annotation.StyleRes;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
@@ -100,9 +101,9 @@ import app.organicmaps.settings.DrivingOptionsActivity;
|
||||
import app.organicmaps.settings.RoadType;
|
||||
import app.organicmaps.settings.SettingsActivity;
|
||||
import app.organicmaps.settings.UnitLocale;
|
||||
import app.organicmaps.universalbuttons.UniversalButton;
|
||||
import app.organicmaps.universalbuttons.UniversalButtonsHolder;
|
||||
import app.organicmaps.universalbuttons.UniversalToggleButton;
|
||||
import app.organicmaps.leftbutton.LeftButton;
|
||||
import app.organicmaps.leftbutton.LeftButtonsHolder;
|
||||
import app.organicmaps.leftbutton.LeftToggleButton;
|
||||
import app.organicmaps.util.Config;
|
||||
import app.organicmaps.util.LocationUtils;
|
||||
import app.organicmaps.util.PowerManagment;
|
||||
@@ -132,9 +133,10 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static app.organicmaps.location.LocationState.FOLLOW;
|
||||
import static app.organicmaps.location.LocationState.FOLLOW_AND_ROTATE;
|
||||
import static app.organicmaps.location.LocationState.LOCATION_TAG;
|
||||
import static app.organicmaps.universalbuttons.UniversalButtonsHolder.BUTTON_ADD_PLACE_CODE;
|
||||
import static app.organicmaps.universalbuttons.UniversalButtonsHolder.BUTTON_RECORD_TRACK_CODE;
|
||||
import static app.organicmaps.universalbuttons.UniversalButtonsHolder.BUTTON_SETTINGS_CODE;
|
||||
import static app.organicmaps.leftbutton.LeftButtonsHolder.BUTTON_ADD_PLACE_CODE;
|
||||
import static app.organicmaps.leftbutton.LeftButtonsHolder.BUTTON_HELP_CODE;
|
||||
import static app.organicmaps.leftbutton.LeftButtonsHolder.BUTTON_RECORD_TRACK_CODE;
|
||||
import static app.organicmaps.leftbutton.LeftButtonsHolder.BUTTON_SETTINGS_CODE;
|
||||
import static app.organicmaps.util.PowerManagment.POWER_MANAGEMENT_TAG;
|
||||
|
||||
public class MwmActivity extends BaseMwmFragmentActivity
|
||||
@@ -208,7 +210,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
|
||||
private int mNavBarHeight;
|
||||
|
||||
private UniversalButtonsHolder buttonsHolder;
|
||||
private LeftButtonsHolder buttonsHolder;
|
||||
|
||||
private PlacePageViewModel mPlacePageViewModel;
|
||||
private MapButtonsViewModel mMapButtonsViewModel;
|
||||
@@ -611,12 +613,12 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
if (activityResult.getResultCode() == Activity.RESULT_OK)
|
||||
{
|
||||
Intent data = activityResult.getData();
|
||||
if (data != null && data.hasExtra(UniversalButtonsHolder.KEY_PREF_UNIVERSAL_BUTTON))
|
||||
if (data != null && data.hasExtra(MwmActivity.this.getString(R.string.pref_left_button)))
|
||||
{
|
||||
MapButtonsController mMapButtonsController = (MapButtonsController) getSupportFragmentManager().findFragmentById(R.id.map_buttons);
|
||||
if (mMapButtonsController != null)
|
||||
{
|
||||
mMapButtonsController.reloadUniversalButton(buttonsHolder.getActiveButton());
|
||||
mMapButtonsController.reloadLeftButton(buttonsHolder.getActiveButton());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -823,9 +825,35 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
|
||||
private void prepareNavigationButtons()
|
||||
{
|
||||
buttonsHolder = UniversalButtonsHolder.getInstance(this);
|
||||
buttonsHolder.registerDefaultUniversalButtons(this);
|
||||
buttonsHolder.registerButton(new UniversalButton()
|
||||
buttonsHolder = LeftButtonsHolder.getInstance(this);
|
||||
buttonsHolder.registerButton(new LeftButton()
|
||||
{
|
||||
@Override
|
||||
public String getCode()
|
||||
{
|
||||
return BUTTON_HELP_CODE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrefsName()
|
||||
{
|
||||
return getString(R.string.help);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawIcon(FloatingActionButton imageView)
|
||||
{
|
||||
imageView.setImageResource(R.drawable.ic_question_mark);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(FloatingActionButton left)
|
||||
{
|
||||
Intent intent = new Intent(MwmActivity.this, HelpActivity.class);
|
||||
MwmActivity.this.startActivity(intent);
|
||||
}
|
||||
});
|
||||
buttonsHolder.registerButton(new LeftButton()
|
||||
{
|
||||
@Override
|
||||
public String getCode()
|
||||
@@ -846,12 +874,12 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(FloatingActionButton universalButtonView)
|
||||
public void onClick(FloatingActionButton left)
|
||||
{
|
||||
onAddPlace();
|
||||
}
|
||||
});
|
||||
buttonsHolder.registerButton(new UniversalButton()
|
||||
buttonsHolder.registerButton(new LeftButton()
|
||||
{
|
||||
@Override
|
||||
public String getCode()
|
||||
@@ -872,13 +900,13 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(FloatingActionButton universalButtonView)
|
||||
public void onClick(FloatingActionButton left)
|
||||
{
|
||||
onOpenSettings();
|
||||
}
|
||||
});
|
||||
|
||||
buttonsHolder.registerButton(new UniversalToggleButton()
|
||||
buttonsHolder.registerButton(new LeftToggleButton()
|
||||
{
|
||||
private boolean isRecording = TrackRecorder.nativeIsTrackRecordingEnabled();
|
||||
|
||||
@@ -906,7 +934,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
imageView.setImageResource(R.drawable.ic_track_recording_off);
|
||||
|
||||
int color = isRecording
|
||||
? Color.parseColor("#0057ff")
|
||||
? ContextCompat.getColor(MwmActivity.this, R.color.active_track_recording)
|
||||
: ThemeUtils.getColor(MwmActivity.this, R.attr.iconTint);
|
||||
|
||||
ColorStateList colorStateList = ColorStateList.valueOf(color);
|
||||
@@ -914,10 +942,10 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(FloatingActionButton universalButtonView)
|
||||
public void onClick(FloatingActionButton left)
|
||||
{
|
||||
onTrackRecordingOptionSelected();
|
||||
drawIcon(universalButtonView);
|
||||
drawIcon(left);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -928,7 +956,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
if (mPreviousMapLayoutMode != layoutMode)
|
||||
{
|
||||
MapButtonsController mapButtonsController = new MapButtonsController();
|
||||
mapButtonsController.setUniversalButton(buttonsHolder.getActiveButton());
|
||||
mapButtonsController.setLeftButton(buttonsHolder.getActiveButton());
|
||||
|
||||
FragmentTransaction transaction = getSupportFragmentManager()
|
||||
.beginTransaction().replace(R.id.map_buttons, mapButtonsController);
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
@@ -24,14 +23,16 @@ import app.organicmaps.util.Utils;
|
||||
import app.organicmaps.util.WindowInsetUtils.ScrollableContentInsetsListener;
|
||||
import app.organicmaps.util.concurrency.ThreadPool;
|
||||
import app.organicmaps.util.concurrency.UiThread;
|
||||
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
|
||||
public class OsmLoginFragment extends BaseMwmToolbarFragment
|
||||
{
|
||||
private ProgressBar mProgress;
|
||||
private Button mLoginButton;
|
||||
private Button mLostPasswordButton;
|
||||
private MaterialButton mLoginButton;
|
||||
private MaterialButton mLostPasswordButton;
|
||||
private TextInputEditText mLoginInput;
|
||||
private TextInputEditText mPasswordInput;
|
||||
|
||||
@@ -51,7 +52,7 @@ public class OsmLoginFragment extends BaseMwmToolbarFragment
|
||||
mPasswordInput = view.findViewById(R.id.osm_password);
|
||||
mLoginButton = view.findViewById(R.id.login);
|
||||
mLostPasswordButton = view.findViewById(R.id.lost_password);
|
||||
Button registerButton = view.findViewById(R.id.register);
|
||||
MaterialButton registerButton = view.findViewById(R.id.register);
|
||||
registerButton.setOnClickListener((v) -> Utils.openUrl(requireActivity(), Constants.Url.OSM_REGISTER));
|
||||
mProgress = view.findViewById(R.id.osm_login_progress);
|
||||
final String dataVersion = DateUtils.getShortDateFormatter().format(Framework.getDataVersion());
|
||||
|
||||
@@ -6,9 +6,7 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -22,15 +20,17 @@ import app.organicmaps.util.WindowInsetUtils;
|
||||
import app.organicmaps.util.concurrency.ThreadPool;
|
||||
import app.organicmaps.util.concurrency.UiThread;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.imageview.ShapeableImageView;
|
||||
import com.google.android.material.textview.MaterialTextView;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
|
||||
public class ProfileFragment extends BaseMwmToolbarFragment
|
||||
{
|
||||
private View mUserInfoBlock;
|
||||
private TextView mEditsSent;
|
||||
private TextView mProfileName;
|
||||
private ImageView mProfileImage;
|
||||
private MaterialTextView mEditsSent;
|
||||
private MaterialTextView mProfileName;
|
||||
private ShapeableImageView mProfileImage;
|
||||
private ProgressBar mProfileInfoLoading;
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package app.organicmaps.leftbutton;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
public interface LeftButton
|
||||
{
|
||||
String getCode();
|
||||
|
||||
String getPrefsName();
|
||||
|
||||
default void drawIcon(FloatingActionButton imageView) {}
|
||||
|
||||
default void onClick(FloatingActionButton leftButtonView) {}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package app.organicmaps.leftbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import app.organicmaps.R;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class LeftButtonsHolder
|
||||
{
|
||||
private static volatile LeftButtonsHolder instance;
|
||||
|
||||
public static final String DISABLE_BUTTON_CODE = "disable";
|
||||
public static final String BUTTON_HELP_CODE = "help";
|
||||
public static final String BUTTON_SETTINGS_CODE = "settings";
|
||||
public static final String BUTTON_ADD_PLACE_CODE = "add-place";
|
||||
public static final String BUTTON_RECORD_TRACK_CODE = "record-track";
|
||||
private static final String DEFAULT_BUTTON_CODE = BUTTON_HELP_CODE;
|
||||
|
||||
private final String leftButtonPreferenceKey;
|
||||
|
||||
private final SharedPreferences prefs;
|
||||
private final Map<String, LeftButton> availableButtons = new LinkedHashMap<>();
|
||||
|
||||
private LeftButtonsHolder(Context context)
|
||||
{
|
||||
this.prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
this.leftButtonPreferenceKey = context.getString(R.string.pref_left_button);
|
||||
initDisableButton(context);
|
||||
}
|
||||
|
||||
public void registerButton(LeftButton button)
|
||||
{
|
||||
availableButtons.put(button.getCode(), button);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public LeftButton getActiveButton()
|
||||
{
|
||||
String activeButtonCode = prefs.getString(leftButtonPreferenceKey, DEFAULT_BUTTON_CODE);
|
||||
if (!TextUtils.isEmpty(activeButtonCode))
|
||||
return availableButtons.get(activeButtonCode);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public Collection<LeftButton> getAllButtons()
|
||||
{
|
||||
return availableButtons.values();
|
||||
}
|
||||
|
||||
public static LeftButtonsHolder getInstance(Context context)
|
||||
{
|
||||
LeftButtonsHolder localInstance = instance;
|
||||
if (localInstance == null)
|
||||
{
|
||||
synchronized (LeftButtonsHolder.class)
|
||||
{
|
||||
localInstance = instance;
|
||||
if (localInstance == null)
|
||||
{
|
||||
instance = localInstance = new LeftButtonsHolder(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
return localInstance;
|
||||
}
|
||||
|
||||
private void initDisableButton(Context context)
|
||||
{
|
||||
availableButtons.put(DISABLE_BUTTON_CODE, new LeftButton()
|
||||
{
|
||||
@Override
|
||||
public String getCode()
|
||||
{
|
||||
return DISABLE_BUTTON_CODE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrefsName()
|
||||
{
|
||||
return context.getString(R.string.pref_left_button_disable);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package app.organicmaps.leftbutton;
|
||||
|
||||
public interface LeftToggleButton extends LeftButton
|
||||
{
|
||||
void setChecked(boolean checked);
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
package app.organicmaps.maplayer;
|
||||
|
||||
import static app.organicmaps.leftbutton.LeftButtonsHolder.BUTTON_HELP_CODE;
|
||||
import static app.organicmaps.leftbutton.LeftButtonsHolder.DISABLE_BUTTON_CODE;
|
||||
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
@@ -33,8 +36,8 @@ import app.organicmaps.maplayer.isolines.IsolinesManager;
|
||||
import app.organicmaps.maplayer.subway.SubwayManager;
|
||||
import app.organicmaps.maplayer.traffic.TrafficManager;
|
||||
import app.organicmaps.routing.RoutingController;
|
||||
import app.organicmaps.universalbuttons.UniversalButton;
|
||||
import app.organicmaps.universalbuttons.UniversalToggleButton;
|
||||
import app.organicmaps.leftbutton.LeftButton;
|
||||
import app.organicmaps.leftbutton.LeftToggleButton;
|
||||
import app.organicmaps.util.Config;
|
||||
import app.organicmaps.util.ThemeUtils;
|
||||
import app.organicmaps.util.UiUtils;
|
||||
@@ -81,11 +84,11 @@ public class MapButtonsController extends Fragment
|
||||
private final Observer<Boolean> mTrackRecorderObserver = (enable) -> {
|
||||
updateMenuBadge(enable);
|
||||
showButton(enable, MapButtons.trackRecordingStatus);
|
||||
updateUniversalButtonState(enable);
|
||||
updateLeftButtonToggleState(enable);
|
||||
};
|
||||
private final Observer<Integer> mTopButtonMarginObserver = this::updateTopButtonsMargin;
|
||||
|
||||
private UniversalButton mUniversalButton;
|
||||
private LeftButton mLeftButton;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@@ -153,7 +156,7 @@ public class MapButtonsController extends Fragment
|
||||
private void initBottomButtons()
|
||||
{
|
||||
// universal button
|
||||
applyUniversalButton();
|
||||
applyLeftButton();
|
||||
|
||||
// bookmarks button
|
||||
View bookmarksButton = mFrame.findViewById(R.id.btn_bookmarks);
|
||||
@@ -190,32 +193,32 @@ public class MapButtonsController extends Fragment
|
||||
}
|
||||
}
|
||||
|
||||
private void applyUniversalButton()
|
||||
private void applyLeftButton()
|
||||
{
|
||||
FloatingActionButton universalButtonView = mFrame.findViewById(R.id.universal_button);
|
||||
if (universalButtonView != null && mUniversalButton != null)
|
||||
FloatingActionButton leftButtonView = mFrame.findViewById(R.id.left_button);
|
||||
if (leftButtonView != null && mLeftButton != null && !mLeftButton.getCode().equals(DISABLE_BUTTON_CODE))
|
||||
{
|
||||
UiUtils.show(universalButtonView);
|
||||
UiUtils.show(leftButtonView);
|
||||
|
||||
Context context = getContext();
|
||||
if (context == null)
|
||||
return;
|
||||
|
||||
universalButtonView.setImageTintList(ColorStateList.valueOf(ThemeUtils.getColor(context, R.attr.iconTint)));
|
||||
leftButtonView.setImageTintList(ColorStateList.valueOf(ThemeUtils.getColor(context, R.attr.iconTint)));
|
||||
|
||||
// Christmas tree with help button
|
||||
if (Config.isNY() &&
|
||||
mUniversalButton.getCode().equals("help") &&
|
||||
mLeftButton.getCode().equals(BUTTON_HELP_CODE) &&
|
||||
!TextUtils.isEmpty(Config.getDonateUrl(requireContext()))
|
||||
)
|
||||
{
|
||||
universalButtonView.setImageResource(R.drawable.ic_christmas_tree);
|
||||
universalButtonView.setOnClickListener((v) -> mMapButtonClickListener.onMapButtonClick(MapButtons.help));
|
||||
leftButtonView.setImageResource(R.drawable.ic_christmas_tree);
|
||||
leftButtonView.setOnClickListener((v) -> mMapButtonClickListener.onMapButtonClick(MapButtons.help));
|
||||
}
|
||||
else
|
||||
{
|
||||
mUniversalButton.drawIcon(universalButtonView);
|
||||
universalButtonView.setOnClickListener((v) -> mUniversalButton.onClick(universalButtonView));
|
||||
mLeftButton.drawIcon(leftButtonView);
|
||||
leftButtonView.setOnClickListener((v) -> mLeftButton.onClick(leftButtonView));
|
||||
}
|
||||
// else
|
||||
// {
|
||||
@@ -225,9 +228,9 @@ public class MapButtonsController extends Fragment
|
||||
// if (!ThemeUtils.isNightTheme(requireContext()))
|
||||
// helpButton.getDrawable().setTintList(null);
|
||||
}
|
||||
else if (universalButtonView != null)
|
||||
else if (leftButtonView != null)
|
||||
{
|
||||
UiUtils.hide(universalButtonView);
|
||||
UiUtils.hide(leftButtonView);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -504,24 +507,24 @@ public class MapButtonsController extends Fragment
|
||||
mSearchWheel.reset();
|
||||
}
|
||||
|
||||
public void setUniversalButton(UniversalButton universalButton)
|
||||
public void setLeftButton(LeftButton leftButton)
|
||||
{
|
||||
this.mUniversalButton = universalButton;
|
||||
this.mLeftButton = leftButton;
|
||||
}
|
||||
|
||||
public void reloadUniversalButton(UniversalButton universalButton)
|
||||
public void reloadLeftButton(LeftButton leftButton)
|
||||
{
|
||||
setUniversalButton(universalButton);
|
||||
applyUniversalButton();
|
||||
setLeftButton(leftButton);
|
||||
applyLeftButton();
|
||||
}
|
||||
|
||||
private void updateUniversalButtonState(boolean isEnabled)
|
||||
private void updateLeftButtonToggleState(boolean isEnabled)
|
||||
{
|
||||
if (mUniversalButton instanceof UniversalToggleButton)
|
||||
if (mLeftButton instanceof LeftToggleButton)
|
||||
{
|
||||
((UniversalToggleButton) mUniversalButton).setChecked(isEnabled);
|
||||
((LeftToggleButton) mLeftButton).setChecked(isEnabled);
|
||||
|
||||
reloadUniversalButton(mUniversalButton);
|
||||
reloadLeftButton(mLeftButton);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.organicmaps.settings;
|
||||
|
||||
import static app.organicmaps.leftbutton.LeftButtonsHolder.DISABLE_BUTTON_CODE;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -25,8 +27,8 @@ import app.organicmaps.help.HelpActivity;
|
||||
import app.organicmaps.location.LocationHelper;
|
||||
import app.organicmaps.location.LocationProviderFactory;
|
||||
import app.organicmaps.sdk.routing.RoutingOptions;
|
||||
import app.organicmaps.universalbuttons.UniversalButton;
|
||||
import app.organicmaps.universalbuttons.UniversalButtonsHolder;
|
||||
import app.organicmaps.leftbutton.LeftButton;
|
||||
import app.organicmaps.leftbutton.LeftButtonsHolder;
|
||||
import app.organicmaps.util.Config;
|
||||
import app.organicmaps.util.NetworkPolicy;
|
||||
import app.organicmaps.util.PowerManagment;
|
||||
@@ -39,6 +41,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -73,26 +77,22 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
||||
initSearchPrivacyPrefsCallbacks();
|
||||
initScreenSleepEnabledPrefsCallbacks();
|
||||
initShowOnLockScreenPrefsCallbacks();
|
||||
initUniversalButtonPrefs();
|
||||
initLeftButtonPrefs();
|
||||
}
|
||||
|
||||
private void initUniversalButtonPrefs()
|
||||
private void initLeftButtonPrefs()
|
||||
{
|
||||
final ListPreference pref = getPreference(UniversalButtonsHolder.KEY_PREF_UNIVERSAL_BUTTON);
|
||||
UniversalButtonsHolder holder = UniversalButtonsHolder.getInstance(requireContext());
|
||||
final String leftButtonPreferenceKey = getString(R.string.pref_left_button);
|
||||
final ListPreference pref = getPreference(leftButtonPreferenceKey);
|
||||
LeftButtonsHolder holder = LeftButtonsHolder.getInstance(requireContext());
|
||||
|
||||
UniversalButton currentButton = holder.getActiveButton();
|
||||
Collection<UniversalButton> buttons = holder.getAllButtons();
|
||||
LeftButton currentButton = holder.getActiveButton();
|
||||
Collection<LeftButton> buttons = holder.getAllButtons();
|
||||
|
||||
List<String> entryList = new ArrayList<>(buttons.size() + 1);
|
||||
List<String> valueList = new ArrayList<>(buttons.size() + 1);
|
||||
List<String> entryList = new ArrayList<>(buttons.size());
|
||||
List<String> valueList = new ArrayList<>(buttons.size());
|
||||
|
||||
String notDisplayId = "not_display";
|
||||
|
||||
entryList.add(requireContext().getString(R.string.pref_universal_button_not_display));
|
||||
valueList.add(notDisplayId);
|
||||
|
||||
for (UniversalButton button : buttons)
|
||||
for (LeftButton button : buttons)
|
||||
{
|
||||
entryList.add(button.getPrefsName());
|
||||
valueList.add(button.getCode());
|
||||
@@ -108,8 +108,8 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
||||
}
|
||||
else
|
||||
{
|
||||
pref.setSummary(R.string.pref_universal_button_not_display);
|
||||
pref.setValue(notDisplayId);
|
||||
pref.setSummary(R.string.pref_left_button_disable);
|
||||
pref.setValue(DISABLE_BUTTON_CODE);
|
||||
}
|
||||
|
||||
pref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
@@ -120,7 +120,7 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
||||
}
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(UniversalButtonsHolder.KEY_PREF_UNIVERSAL_BUTTON, newValue.toString());
|
||||
intent.putExtra(leftButtonPreferenceKey, newValue.toString());
|
||||
|
||||
requireActivity().setResult(-1, intent);
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package app.organicmaps.universalbuttons;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
public interface UniversalButton
|
||||
{
|
||||
String getCode();
|
||||
|
||||
String getPrefsName();
|
||||
|
||||
void drawIcon(FloatingActionButton imageView);
|
||||
|
||||
void onClick(FloatingActionButton universalButtonView);
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package app.organicmaps.universalbuttons;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import app.organicmaps.MwmApplication;
|
||||
import app.organicmaps.R;
|
||||
import app.organicmaps.help.HelpActivity;
|
||||
|
||||
public class UniversalButtonsHolder
|
||||
{
|
||||
private static volatile UniversalButtonsHolder instance;
|
||||
|
||||
public static final String KEY_PREF_UNIVERSAL_BUTTON = "universal_button";
|
||||
private static final String BUTTON_HELP_CODE = "help";
|
||||
public static final String BUTTON_SETTINGS_CODE = "settings";
|
||||
public static final String BUTTON_ADD_PLACE_CODE = "add-place";
|
||||
public static final String BUTTON_RECORD_TRACK_CODE = "record-track";
|
||||
private static final String DEFAULT_BUTTON_CODE = BUTTON_HELP_CODE;
|
||||
|
||||
private final Context context;
|
||||
private final SharedPreferences prefs;
|
||||
private final Map<String, UniversalButton> availableButtons = new HashMap<>();
|
||||
|
||||
private UniversalButtonsHolder(Context context)
|
||||
{
|
||||
this.context = context;
|
||||
this.prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
}
|
||||
|
||||
public void registerButton(UniversalButton button)
|
||||
{
|
||||
availableButtons.put(button.getCode(), button);
|
||||
}
|
||||
|
||||
public void registerDefaultUniversalButtons(Context context)
|
||||
{
|
||||
registerButton(new UniversalButton()
|
||||
{
|
||||
@Override
|
||||
public String getCode()
|
||||
{
|
||||
return BUTTON_HELP_CODE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrefsName()
|
||||
{
|
||||
return context.getString(R.string.help);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawIcon(FloatingActionButton imageView)
|
||||
{
|
||||
imageView.setImageResource(R.drawable.ic_question_mark);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(FloatingActionButton universalButtonView)
|
||||
{
|
||||
Intent intent = new Intent(context, HelpActivity.class);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public UniversalButton getActiveButton()
|
||||
{
|
||||
String activeButtonCode = prefs.getString(KEY_PREF_UNIVERSAL_BUTTON, DEFAULT_BUTTON_CODE);
|
||||
if (!TextUtils.isEmpty(activeButtonCode))
|
||||
return availableButtons.get(activeButtonCode);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public Collection<UniversalButton> getAllButtons()
|
||||
{
|
||||
return availableButtons.values();
|
||||
}
|
||||
|
||||
public static UniversalButtonsHolder getInstance(Context context)
|
||||
{
|
||||
UniversalButtonsHolder localInstance = instance;
|
||||
if (localInstance == null)
|
||||
{
|
||||
synchronized (UniversalButtonsHolder.class)
|
||||
{
|
||||
localInstance = instance;
|
||||
if (localInstance == null)
|
||||
{
|
||||
instance = localInstance = new UniversalButtonsHolder(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
return localInstance;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package app.organicmaps.universalbuttons;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
public interface UniversalToggleButton extends UniversalButton
|
||||
{
|
||||
void setChecked(boolean checked);
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
android:viewportHeight="80">
|
||||
<path
|
||||
android:pathData="m4.858,10.076c0,-5.523 4.477,-10 10,-10h60c5.523,0 10,4.477 10,10v60c0,5.523 -4.477,10 -10,10h-60c-5.523,0 -10,-4.477 -10,-10z"
|
||||
android:fillColor="#0057ff"
|
||||
android:fillColor="@color/active_track_recording"
|
||||
android:fillAlpha="0.78" />
|
||||
<path
|
||||
android:pathData="m78.907,32.87 l-31.833,13.318 13.335,5.772 6.188,13.147z"
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="12dp" />
|
||||
<solid android:color="#0057ff" />
|
||||
<solid android:color="@color/active_track_recording" />
|
||||
</shape>
|
||||
@@ -52,5 +52,15 @@
|
||||
android:layout_marginTop="@dimen/margin_direction_mid"
|
||||
android:textSize="@dimen/text_direction_dist"
|
||||
tools:text="9000 km"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv__azimuth"
|
||||
style="@style/MwmWidget.TextView.Direction"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_direction_small"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="@dimen/text_direction_subtitle"
|
||||
tools:text="123.4°" />
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/margin_base">
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/textView2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -47,7 +47,7 @@
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="@dimen/osm_logo"
|
||||
android:layout_height="@dimen/osm_logo"
|
||||
@@ -56,7 +56,7 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/osm_presentation"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -108,7 +108,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/margin_base">
|
||||
<Button
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/login"
|
||||
style="@style/MwmWidget.Button.Accent"
|
||||
android:layout_width="match_parent"
|
||||
@@ -123,7 +123,7 @@
|
||||
android:elevation="@dimen/design_fab_elevation"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
<Button
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/lost_password"
|
||||
style="@style/MwmWidget.Button"
|
||||
android:layout_width="match_parent"
|
||||
@@ -138,14 +138,14 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_base"
|
||||
android:layout_marginBottom="@dimen/margin_base" />
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@string/robotoRegular"
|
||||
android:text="@string/no_osm_account"
|
||||
android:textAppearance="@style/MwmTextAppearance.Body2"
|
||||
android:textColor="?android:textColorPrimary" />
|
||||
<Button
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/register"
|
||||
style="@style/MwmWidget.Button.Accent"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
<include
|
||||
android:id="@+id/universal_button"
|
||||
android:id="@+id/left_button"
|
||||
layout="@layout/map_buttons_help"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -25,7 +25,7 @@
|
||||
android:layout_marginStart="@dimen/margin_base_plus"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_goneMarginStart="@dimen/margin_half_plus"
|
||||
app:layout_constraintStart_toEndOf="@+id/universal_button" />
|
||||
app:layout_constraintStart_toEndOf="@+id/left_button" />
|
||||
<include
|
||||
android:id="@+id/btn_bookmarks"
|
||||
layout="@layout/map_buttons_bookmarks_square"
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/margin_base"
|
||||
tools:ignore="ScrollViewSize">
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/textView2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -39,7 +39,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/margin_base">
|
||||
<ImageView
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/osm_logo"
|
||||
android:layout_width="@dimen/osm_logo"
|
||||
android:layout_height="@dimen/osm_logo"
|
||||
@@ -48,7 +48,7 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/osm_presentation"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -92,7 +92,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/margin_base">
|
||||
<Button
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/login"
|
||||
style="@style/MwmWidget.Button.Accent"
|
||||
android:layout_width="match_parent"
|
||||
@@ -107,7 +107,7 @@
|
||||
android:elevation="@dimen/design_fab_elevation"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
<Button
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/lost_password"
|
||||
style="@style/MwmWidget.Button"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -122,14 +122,14 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_base"
|
||||
android:layout_marginBottom="@dimen/margin_base" />
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@string/robotoRegular"
|
||||
android:text="@string/no_osm_account"
|
||||
android:textAppearance="@style/MwmTextAppearance.Body2"
|
||||
android:textColor="?android:textColorPrimary" />
|
||||
<Button
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/register"
|
||||
style="@style/MwmWidget.Button.Accent"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
style="@style/MwmWidget.ToolbarStyle"
|
||||
android:layout_width="match_parent"
|
||||
@@ -15,7 +15,7 @@
|
||||
android:gravity="end|center_vertical"
|
||||
android:theme="@style/MwmWidget.ToolbarTheme"
|
||||
tools:ignore="UnusedAttribute">
|
||||
<ImageView
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/logout"
|
||||
android:layout_width="?actionBarSize"
|
||||
android:layout_height="?actionBarSize"
|
||||
@@ -24,7 +24,7 @@
|
||||
android:scaleType="center"
|
||||
app:srcCompat="@drawable/ic_logout"
|
||||
android:contentDescription="@string/logout" />
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -44,7 +44,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="horizontal">
|
||||
<ImageView
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/user_profile_image"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="match_parent"
|
||||
@@ -57,7 +57,7 @@
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/user_profile_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -68,7 +68,7 @@
|
||||
android:autoSizeTextType="uniform"
|
||||
android:textSize="25sp"
|
||||
tools:text="Long Username" />
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/user_sent_edits"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -76,7 +76,7 @@
|
||||
android:textSize="35sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="2,000,000" />
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/verified_changes_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -93,7 +93,7 @@
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/osm_history"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -110,7 +110,7 @@
|
||||
android:textColor="?colorAccent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/osm_notes"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -126,7 +126,7 @@
|
||||
android:textColor="?colorAccent"
|
||||
app:layout_constraintTop_toBottomOf="@id/osm_history" />
|
||||
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/about_osm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
<include
|
||||
android:id="@+id/universal_button"
|
||||
android:id="@+id/left_button"
|
||||
layout="@layout/map_buttons_help"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -32,7 +32,7 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btn_bookmarks"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/universal_button" />
|
||||
app:layout_constraintStart_toEndOf="@+id/left_button" />
|
||||
<include
|
||||
android:id="@+id/btn_bookmarks"
|
||||
layout="@layout/map_buttons_bookmarks_square"
|
||||
|
||||
@@ -879,4 +879,7 @@
|
||||
<string name="error_enter_correct_fediverse_page">Zadejte platné uživatelské jméno nebo webovou adresu Mastodonu</string>
|
||||
<string name="bluesky">Bluesky</string>
|
||||
<string name="error_enter_correct_bluesky_page">Zadejte platné uživatelské jméno nebo webovou adresu Bluesky</string>
|
||||
<string name="saved">Uloženo</string>
|
||||
<string name="pref_left_button_disable">Zakázat</string>
|
||||
<string name="pref_left_button_title">Nastavení levého tlačítka</string>
|
||||
</resources>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<!-- Settings/Downloader - info for country when download fails -->
|
||||
<string name="download_has_failed">Herunterladen fehlgeschlagen. Antippen für einen neuen Versuch.</string>
|
||||
<!-- Settings/Downloader - info for country which started downloading -->
|
||||
<string name="downloading">Wird heruntergeladen…</string>
|
||||
<string name="downloading">Wird heruntergeladen …</string>
|
||||
<!-- Choose measurement on first launch alert - choose metric system button -->
|
||||
<string name="kilometres">Kilometer</string>
|
||||
<!-- Choose measurement on first launch alert - choose imperial system button -->
|
||||
@@ -888,4 +888,7 @@
|
||||
<string name="error_enter_correct_bluesky_page">Geben Sie einen gültigen Bluesky-Nutzernamen oder Webadresse ein</string>
|
||||
<string name="bluesky">Bluesky</string>
|
||||
<string name="error_enter_correct_fediverse_page">Geben Sie einen gültigen Mastodon-Nutzernamen oder Webadresse an</string>
|
||||
<string name="pref_left_button_title">Einstellung linker Knopf</string>
|
||||
<string name="saved">Gespeichert</string>
|
||||
<string name="pref_left_button_disable">Deaktivieren</string>
|
||||
</resources>
|
||||
|
||||
@@ -1395,7 +1395,7 @@
|
||||
<string name="type.sport.diving">Tauchen</string>
|
||||
<string name="type.amenity.love_hotel">Stundenhotel</string>
|
||||
<string name="type.landuse.plant_nursery">Gartenbau</string>
|
||||
<string name="type.leisure.firepit">Feuerschale</string>
|
||||
<string name="type.leisure.firepit">Feuerstelle</string>
|
||||
<string name="type.barrier.guard_rail">Leitplanke</string>
|
||||
<string name="type.highway.ladder">Leiter</string>
|
||||
<string name="type.amenity.studio">Studio</string>
|
||||
|
||||
@@ -38,15 +38,15 @@
|
||||
<string name="try_again">Intentar otra vez</string>
|
||||
<string name="about_menu_title">Acerca de CoMaps</string>
|
||||
<!-- Text in About screen -->
|
||||
<string name="about_headline">Gratis para todos, hecho con amor</string>
|
||||
<string name="about_headline">Proyecto abierto impulsado por la comunidad</string>
|
||||
<!-- Text in About screen -->
|
||||
<string name="about_proposition_1">• Sin anuncios, sin rastreo, sin recopilación de datos</string>
|
||||
<string name="about_proposition_1">• Fácil de utilizar y depurado</string>
|
||||
<!-- Text in About screen -->
|
||||
<string name="about_proposition_2">• Consumo de batería mínimo, funciona sin conexión</string>
|
||||
<string name="about_proposition_2">• Enfocado a la privacidad y sin anuncios</string>
|
||||
<!-- Text in About screen -->
|
||||
<string name="about_proposition_3">• Rápido, minimalista, desarrollado por la comunidad</string>
|
||||
<string name="about_proposition_3">• No necesita conexión a internet, rápido y compacto</string>
|
||||
<!-- Text in About screen -->
|
||||
<string name="about_developed_by_enthusiasts">Aplicación de código abierto creada por entusiastas y voluntarios.</string>
|
||||
<string name="about_developed_by_enthusiasts">Completamente de código abierto, sin ánimo de lucro, y con procesos de toma de decisiones y finanzas transparentes.</string>
|
||||
<!-- The button that opens system location settings -->
|
||||
<string name="location_settings">Ajustes de ubicación</string>
|
||||
<string name="close">Cerrar</string>
|
||||
@@ -302,9 +302,9 @@
|
||||
<!-- Item in context menu. -->
|
||||
<string name="downloader_update_map">Actualizar mapa</string>
|
||||
<!-- Preference title -->
|
||||
<string name="google_play_services">Servicios de localización de Google Play</string>
|
||||
<string name="google_play_services">Servicio de localización de Google Fused</string>
|
||||
<!-- Preference text -->
|
||||
<string name="pref_use_google_play">Determina rápidamente su ubicación aproximada mediante Bluetooth, WiFi o red móvil</string>
|
||||
<string name="pref_use_google_play">Un servicio propietario que combina señal GPS, Wi-Fi, redes móviles, etc. para determinar tu ubicación con más precisión. Puede que se conecte a servidores de Google.</string>
|
||||
<!-- Preference title -->
|
||||
<!-- Preference description -->
|
||||
<!-- Text for routing error dialog -->
|
||||
@@ -886,4 +886,12 @@
|
||||
<string name="comma_separated_pair">%1$s, %2$s</string>
|
||||
<string name="level_value_generic">Nivel: %s</string>
|
||||
<string name="editor_line_social_network">LINE</string>
|
||||
<string name="saved">Guardado</string>
|
||||
<string name="codeberg">Codeberg</string>
|
||||
<string name="error_enter_correct_fediverse_page">Introduce un nombre de usuario o una dirección web de Mastodon válidos</string>
|
||||
<string name="instagram_url">https://www.instagram.com/comapscommunity</string>
|
||||
<string name="app_site_url">https://comaps.app/</string>
|
||||
<string name="bluesky">Bluesky</string>
|
||||
<string name="error_enter_correct_bluesky_page">Introduce un nombre de usuario o una dirección web de Bluesky válidos</string>
|
||||
<string name="telegram_url">https://t.me/CoMapsApp/</string>
|
||||
</resources>
|
||||
|
||||
@@ -1395,4 +1395,11 @@
|
||||
<string name="type.fee.yes">$</string>
|
||||
<string name="type.railway.narrow_gauge.tunnel">Túnel de vía férrea estrecha</string>
|
||||
<string name="type.railway.light_rail.bridge">Puente de tren ligero</string>
|
||||
<string name="type.highway.ladder">Escalera</string>
|
||||
<string name="type.amenity.studio">Estudio</string>
|
||||
<string name="type.landuse.plant_nursery">Vivero de plantas</string>
|
||||
<string name="type.barrier.guard_rail">Guardarraíl</string>
|
||||
<string name="type.sport.diving">Buceo</string>
|
||||
<string name="type.leisure.firepit">Fogón</string>
|
||||
<string name="type.amenity.love_hotel">Hotel del amor</string>
|
||||
</resources>
|
||||
|
||||
@@ -883,4 +883,7 @@
|
||||
<string name="error_enter_correct_fediverse_page">Sisesta korrektne Mastodoni kasutajanimi või veebiaadress</string>
|
||||
<string name="bluesky">Bluesky</string>
|
||||
<string name="error_enter_correct_bluesky_page">Sisesta korrektne Bluesky kasutajanimi või veebiaadress</string>
|
||||
<string name="saved">Salvestatud</string>
|
||||
<string name="pref_left_button_disable">Keela</string>
|
||||
<string name="pref_left_button_title">Vasaku nupu seadistused</string>
|
||||
</resources>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<!-- Text in About screen -->
|
||||
<string name="about_proposition_3">• Yhteydetön tila, nopea ja kompakti</string>
|
||||
<!-- Text in About screen -->
|
||||
<string name="about_developed_by_enthusiasts">Harrastajien ja vapaaehtoisten luoma avoimen lähdekoodin sovellus.</string>
|
||||
<string name="about_developed_by_enthusiasts">Täysin avoimeen lähdekoodiin perustuva, voittoa tavoittelematon sekä läpinäkyvä päätöksenteko ja talous.</string>
|
||||
<!-- The button that opens system location settings -->
|
||||
<string name="location_settings">Sijaintiasetukset</string>
|
||||
<string name="close">Sulje</string>
|
||||
@@ -304,9 +304,9 @@
|
||||
<!-- Item in context menu. -->
|
||||
<string name="downloader_update_map">Päivitä kartta</string>
|
||||
<!-- Preference title -->
|
||||
<string name="google_play_services">Google Play Sijaintipalvelut</string>
|
||||
<string name="google_play_services">Google Fused Sijaintipalvelu</string>
|
||||
<!-- Preference text -->
|
||||
<string name="pref_use_google_play">Määritä nopeasti likimääräinen sijaintisi Bluetoothin, WiFin tai matkapuhelinverkon kautta</string>
|
||||
<string name="pref_use_google_play">Suljetun lähdekoodin palvelu, joka yhdistää GPS:n, Wi-Fi:n ja mobiiliverkot yms. löytääksesi sijaintisi tarkemmin. Se voi ottaa yhteyttä Googlen palvelimiin.</string>
|
||||
<!-- Preference title -->
|
||||
<!-- Preference description -->
|
||||
<!-- Text for routing error dialog -->
|
||||
@@ -492,7 +492,7 @@
|
||||
<string name="placepage_add_business_button">Lisää organisaatio</string>
|
||||
<string name="message_invalid_feature_position">Kohdetta ei voida asettaa tänne</string>
|
||||
<!-- Text in About and OSM Login screens. First %@ is replaced by a local, human readable date. -->
|
||||
<string name="osm_presentation">Yhteisön luomat OpenStreetMap-tiedot %s:sta alkaen. Lisätietoja kartan muokkaamisesta ja päivittämisestä osoitteessa OpenStreetMap.org.</string>
|
||||
<string name="osm_presentation">Yhteisön luomat OpenStreetMap-tiedot %s:sta alkaen. Lisätietoja kartan muokkaamisesta ja päivittämisestä osoitteessa OpenStreetMap.org</string>
|
||||
<!-- OSM explanation on Android login screen -->
|
||||
<string name="login_osm_presentation">OpenStreetMap.org (OSM) on yhteisöprojekti, jonka tarkoituksena on rakentaa ilmainen ja avoin kartta. Se on CoMapsin tärkein karttatietojen lähde ja toimii samalla tavalla kuin Wikipedia. Voit lisätä tai muokata paikkoja, ja ne tulevat miljoonien käyttäjien saataville kaikkialla maailmassa. \nLiity yhteisöön ja auta tekemään parempi kartta kaikille!</string>
|
||||
<string name="login_to_make_edits_visible">Luo OpenStreetMap-tili tai kirjaudu sisään, jotta voit julkaista karttamuokkauksesi maailmalle.</string>
|
||||
@@ -511,7 +511,7 @@
|
||||
<!-- Title for OSM note section in the editor -->
|
||||
<string name="editor_other_info">Huomautus OpenStreetMapin vapaaehtoisille (valinnainen)</string>
|
||||
<!-- Hint of the input field in the OSM note section of the editor -->
|
||||
<string name="editor_note_hint">Kuvaile kartassa olevia virheitä tai asioita, joita ei voi muokata CoMapsilla.</string>
|
||||
<string name="editor_note_hint">Kuvaile kartassa olevia virheitä tai asioita, joita ei voi muokata CoMapsilla</string>
|
||||
<!-- Information about OSM at the top of the editing page -->
|
||||
<string name="editor_about_osm">Muokkauksesi ladataan julkiseen <a href="https://wiki.openstreetmap.org/wiki/About_OpenStreetMap">OpenStreetMap</a>-tietokantaan. Älä lisää henkilökohtaisia tai tekijänoikeudella suojattuja tietoja.</string>
|
||||
<string name="editor_more_about_osm">Lisätietoja OpenStreetMap:sta</string>
|
||||
@@ -685,7 +685,7 @@
|
||||
<string name="avoid_ferry">Vältä lautan käyttöä</string>
|
||||
<string name="avoid_motorways">Vältä moottoritietä</string>
|
||||
<string name="unable_to_calc_alert_title">Reittiä ei voi luoda</string>
|
||||
<string name="unable_to_calc_alert_subtitle">Valitettavasti emme voineet luoda reittiä valituilla vaihtoehdoilla. Vaihda asetuksia ja yritä uudelleen</string>
|
||||
<string name="unable_to_calc_alert_subtitle">Valitettavasti emme voineet luoda reittiä valituilla vaihtoehdoilla. Tämä voi johtua reittiasetuksista tai OpenStreetMap:n puuttuvista tiedoista. Vaihda reititysasetuksia ja yritä uudelleen.</string>
|
||||
<string name="define_to_avoid_btn">Määritä vältettävät tiet</string>
|
||||
<string name="change_driving_options_btn">Reititysvalinnat ovat päällä</string>
|
||||
<string name="toll_road">Maksullinen tie</string>
|
||||
@@ -799,7 +799,7 @@
|
||||
<!-- App tip #09 -->
|
||||
<string name="app_tip_09">Päätavoitteemme on rakentaa nopeita, yksityisyyteen keskittyviä, helppokäyttöisiä karttoja, joista pidät.</string>
|
||||
<!-- Text on the Android Auto or CarPlay placeholder screen that maps are displayed on the phone screen -->
|
||||
<string name="car_used_on_the_phone_screen">Käytät nyt puhelimen näytöllä CoMaps -palvelua.</string>
|
||||
<string name="car_used_on_the_phone_screen">Käytät nyt CoMapsia puhelimen näytöllä</string>
|
||||
<!-- Text on the phone placeholder screen that maps are displayed on the car screen -->
|
||||
<string name="car_used_on_the_car_screen">Käytät nyt CoMaps auton näytöllä</string>
|
||||
<!-- Displayed on the phone screen. Android Auto connected -->
|
||||
@@ -858,7 +858,7 @@
|
||||
<!-- Message for the toast when saving the track recording is finished but nothing to save. -->
|
||||
<string name="track_recording_toast_nothing_to_save">Reitti on tyhjä - ei mitään tallennettavaa</string>
|
||||
<!-- Error message when there are no File Manager apps installed to select a folder when importing Bookmarks and Tracks -->
|
||||
<string name="error_no_file_manager_app">Kansiovalintaikkunaa ei voida näyttää, koska laitteeseen ei ole asennettu sopivaa sovellusta. Asenna tiedostonhallintasovellus ja yritä uudelleen</string>
|
||||
<string name="error_no_file_manager_app">Kansiovalintaikkunaa ei voida näyttää, koska laitteeseen ei ole asennettu sopivaa sovellusta. Asenna tiedostonhallintasovellus ja yritä uudelleen.</string>
|
||||
<string name="choose_color">Valitse väri</string>
|
||||
<string name="edit_track">Muokkaa reittiä</string>
|
||||
<string name="uri_open_location_failed">Ei asennettua sovellusta, joka voi avata sijainnin</string>
|
||||
@@ -888,4 +888,7 @@
|
||||
<string name="error_enter_correct_bluesky_page">Kirjoita kelvollinen Bluesky-käyttäjänimi tai verkko-osoite</string>
|
||||
<string name="instagram_url">https://www.instagram.com/comapscommunity</string>
|
||||
<string name="app_site_url">https://comaps.app/</string>
|
||||
<string name="pref_left_button_title">Vasemman painikkeen asettelu</string>
|
||||
<string name="pref_left_button_disable">Poista käytöstä</string>
|
||||
<string name="saved">Tallennettu</string>
|
||||
</resources>
|
||||
|
||||
@@ -1312,4 +1312,30 @@
|
||||
<string name="type.route">Reitti</string>
|
||||
<string name="type.boundary.administrative.3">Alueellinen raja</string>
|
||||
<string name="type.boundary.administrative.4">Alueellinen raja</string>
|
||||
<string name="type.aeroway">Airspace Infrastructure</string>
|
||||
<string name="type.aeroway.apron">Apron</string>
|
||||
<string name="type.amenity.vending_machine.excrement_bags">Koirankakkapussiautomaatti</string>
|
||||
<string name="type.hwtag.yescar">hwtag-yescar</string>
|
||||
<string name="type.amenity.biergarten">Olutpuutarha</string>
|
||||
<string name="type.highway.ladder">Tikapuut</string>
|
||||
<string name="type.hwtag.yesfoot">hwtag-yesfoot</string>
|
||||
<string name="type.landuse.plant_nursery">Kasvitarha</string>
|
||||
<string name="type.highway.world_towns_level">highway-world_towns_level</string>
|
||||
<string name="type.cuisine.fish_and_chips">Fish and Chips</string>
|
||||
<string name="type.highway.world_level">highway-world_level</string>
|
||||
<string name="type.hwtag.bidir_bicycle">hwtag-bidir_bicycle</string>
|
||||
<string name="type.hwtag.private">hwtag-private</string>
|
||||
<string name="type.hwtag">hwtag</string>
|
||||
<string name="type.amenity.studio">Studio</string>
|
||||
<string name="type.barrier.guard_rail">Tiekaide</string>
|
||||
<string name="type.hwtag.onedir_bicycle">hwtag-onedir_bicycle</string>
|
||||
<string name="type.hwtag.lit">hwtag-lit</string>
|
||||
<string name="type.hwtag.nobicycle">hwtag-nobicycle</string>
|
||||
<string name="type.hwtag.nocar">hwtag-nocar</string>
|
||||
<string name="type.hwtag.nofoot">hwtag-nofoot</string>
|
||||
<string name="type.hwtag.oneway">hwtag-oneway</string>
|
||||
<string name="type.hwtag.toll">hwtag-toll</string>
|
||||
<string name="type.hwtag.yesbicycle">hwtag-yesbicycle</string>
|
||||
<string name="type.leisure.firepit">Tulentekopaikka</string>
|
||||
<string name="type.leisure">Leisure</string>
|
||||
</resources>
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
<!-- Search category for parking lots; any changes should be duplicated in categories.txt @category_parking! -->
|
||||
<string name="category_parking">Stationnement</string>
|
||||
<!-- Search category for malls/clothes/shoes/gifts/jewellery/sport shops; any changes should be duplicated in categories.txt @category_shopping! -->
|
||||
<string name="category_shopping">Shopping</string>
|
||||
<string name="category_shopping">Magasins</string>
|
||||
<!-- Search category for second_hand/charity/antique/auction shops; any changes should be duplicated in categories.txt @category_secondhand! -->
|
||||
<string name="category_secondhand">D\'occasion</string>
|
||||
<!-- Search category for places to stay; any changes should be duplicated in categories.txt @category_hotel! -->
|
||||
@@ -619,7 +619,7 @@
|
||||
<string name="bookmark_lists_show_all">Tout afficher</string>
|
||||
<plurals name="bookmarks_places">
|
||||
<item quantity="one">%d signet</item>
|
||||
<item quantity="many"/>
|
||||
<item quantity="many">%d signets</item>
|
||||
<item quantity="other">%d signets</item>
|
||||
</plurals>
|
||||
<string name="bookmarks_create_new_group">Créer une nouvelle liste</string>
|
||||
@@ -638,13 +638,13 @@
|
||||
<string name="profile">Profil OpenStreetMap</string>
|
||||
<plurals name="bookmarks_detect_message">
|
||||
<item quantity="one">%d fichier a été trouvé. Vous le verrez après la conversion.</item>
|
||||
<item quantity="many"/>
|
||||
<item quantity="many">%d fichiers ont été trouvés. Vous les verrez après la conversion.</item>
|
||||
<item quantity="other">%d fichiers ont été trouvés. Vous les verrez après la conversion.</item>
|
||||
</plurals>
|
||||
<string name="restore">Restaurer</string>
|
||||
<plurals name="tracks">
|
||||
<item quantity="one">%d piste</item>
|
||||
<item quantity="many"/>
|
||||
<item quantity="many">%d pistes</item>
|
||||
<item quantity="other">%d pistes</item>
|
||||
</plurals>
|
||||
<!-- Settings privacy group in settings screen -->
|
||||
@@ -894,4 +894,7 @@
|
||||
<string name="bluesky">Bluesky</string>
|
||||
<string name="error_enter_correct_bluesky_page">Entrez un nom d\'utilisateur ou une adresse web Bluesky correct</string>
|
||||
<string name="error_enter_correct_fediverse_page">Entrez un nom d\'utilisateur ou une adresse web Mastodon correct</string>
|
||||
<string name="saved">Enregistré</string>
|
||||
<string name="pref_left_button_title">Configuration du bouton en bas à gauche</string>
|
||||
<string name="pref_left_button_disable">Désactiver</string>
|
||||
</resources>
|
||||
|
||||
@@ -883,4 +883,5 @@
|
||||
<string name="error_enter_correct_bluesky_page">Inserisci un nome utente o un indirizzo web Bluesky valido</string>
|
||||
<string name="instagram_url">https://www.instagram.com/comapscommunity/</string>
|
||||
<string name="telegram_url">https://t.me/CoMapsApp/</string>
|
||||
<string name="saved">Salvato</string>
|
||||
</resources>
|
||||
|
||||
@@ -461,7 +461,7 @@
|
||||
<!-- Text field to enter non-existing street name, below list of known streets around -->
|
||||
<string name="add_street">通りを追加</string>
|
||||
<!-- Error to display when a new street name is not entered in the New street dialog -->
|
||||
<string name="empty_street_name_error">通りの名前を入力してください。</string>
|
||||
<string name="empty_street_name_error">通りの名前を入力してください</string>
|
||||
<string name="choose_language">言語を選択</string>
|
||||
<string name="choose_street">通りを選択</string>
|
||||
<string name="postal_code">郵便番号</string>
|
||||
@@ -485,7 +485,7 @@
|
||||
<string name="editor_add_place_title">追加中</string>
|
||||
<string name="editor_edit_place_name_hint">場所の名前</string>
|
||||
<!-- The second part of the editor_edit_place_name_hint to explain that name should be entered in a local language, see https://wiki.openstreetmap.org/wiki/Key:name -->
|
||||
<string name="editor_default_language_hint">現地の言葉でこう書かれている。</string>
|
||||
<string name="editor_default_language_hint">現地の言葉でこう書かれている</string>
|
||||
<string name="editor_edit_place_category_title">カテゴリ</string>
|
||||
<string name="detailed_problem_description">問題の詳細な説明</string>
|
||||
<string name="editor_report_problem_other_title">異なる問題</string>
|
||||
@@ -494,7 +494,7 @@
|
||||
<!-- Text in About and OSM Login screens. First %@ is replaced by a local, human readable date. -->
|
||||
<string name="osm_presentation">コミュニティが作成した%s時点の OpenStreetMap のデータ。地図の編集や更新の方法については、OpenStreetMap.org を参照してください</string>
|
||||
<!-- OSM explanation on Android login screen -->
|
||||
<string name="login_osm_presentation">OpenStreetMap.org(OSM)は、フリーでオープンな地図を構築するコミュニティ・プロジェクトです。OSMは、CoMapsの地図データの主要なソースであり、ウィキペディアと同じように機能します。あなたが場所を追加したり編集したりすると、世界中の何百万人ものユーザーがその場所を利用できるようになります!</string>
|
||||
<string name="login_osm_presentation">OpenStreetMap.org(OSM)は、フリーでオープンな地図を構築するコミュニティ・プロジェクトです。OSMは、CoMapsの地図データの主要なソースであり、ウィキペディアと同じように機能します。あなたが場所を追加したり編集したりすると、世界中の何百万人ものユーザーがその場所を利用できるようになります。</string>
|
||||
<string name="login_to_make_edits_visible">OpenStreetMapのアカウントを作成するかログインして、あなたの地図編集を世界中に公開しましょう。</string>
|
||||
<!-- Downloaded 10 **of** 20 <- it is that "of" -->
|
||||
<string name="downloader_of">%2$dのうち%1$d</string>
|
||||
@@ -511,7 +511,7 @@
|
||||
<!-- Title for OSM note section in the editor -->
|
||||
<string name="editor_other_info">OpenStreetMapボランティアへの注意(オプション)</string>
|
||||
<!-- Hint of the input field in the OSM note section of the editor -->
|
||||
<string name="editor_note_hint">地図上のエラーやCoMapsで編集できないものについて説明します。</string>
|
||||
<string name="editor_note_hint">地図上のエラーやCoMapsで編集できないものについて説明します</string>
|
||||
<!-- Information about OSM at the top of the editing page -->
|
||||
<string name="editor_about_osm">あなたの編集は公開されている<a href="https://wiki.openstreetmap.org/wiki/JA:参加する">OpenStreetMap</a>データベースにアップロードされます。個人情報や著作権のある情報は追加しないでください。</string>
|
||||
<string name="editor_more_about_osm">OpenStreetMapについての詳細</string>
|
||||
@@ -589,7 +589,7 @@
|
||||
<string name="enable_logging">ログを有効化</string>
|
||||
<!-- Settings: "Send general feedback" button -->
|
||||
<string name="feedback_general">一般的なフィードバック</string>
|
||||
<string name="prefs_languages_information">音声案内にはシステムの TTS を使用します。多くの Android 端末が Google の TTS を使用しており、Google Play (https://play.google.com/store/apps/details?id=com.google.android.tts) からダウンロードや更新を行うことができます。</string>
|
||||
<string name="prefs_languages_information">音声案内にはシステムの TTS を使用します。多くの Android 端末が Google の TTS を使用しており、Google Play (https://play.google.com/store/apps/details?id=com.google.android.tts) からダウンロードや更新を行うことができます</string>
|
||||
<string name="prefs_languages_information_off">いくつかの言語では、アプリストア(Google Play、Galaxy Store 、App Gallery、FDroid)から音声合成または追加の言語パックをインストールする必要があります。\nお使いのデバイスで [設定] → [言語と入力] → [音声] → [音声出力] を開いてください。\nここで音声合成の設定 (たとえば、オフラインで使用する言語パックのダウンロードなど) を管理し、別の音声合成エンジンを選択できます。</string>
|
||||
<string name="prefs_languages_information_off_link">詳細については、このガイドをご確認ください。</string>
|
||||
<string name="transliteration_title">ラテン文字への翻字</string>
|
||||
@@ -607,7 +607,7 @@
|
||||
<!-- Alert to ask user relogin to OpenStreetMap with OAuth2 flow after OAuth1 authentication is deprecated. -->
|
||||
<string name="alert_reauth_message">すべてのマップ編集を自動的にアップロードするには、OpenStreetMapにログインしてください。詳しくは <a href="https://github.com/organicmaps/organicmaps/issues/6144">こちら</a>.</string>
|
||||
<string name="dialog_error_storage_title">ストレージアクセスの問題</string>
|
||||
<string name="dialog_error_storage_message">外部ストレージは使用できません。おそらく SD カードが取り外されたか、破損しているか、あるいはファイルシステムが読み取り専用になっています。SD カードを確認するか、support@comaps.app までご連絡ください。</string>
|
||||
<string name="dialog_error_storage_message">外部ストレージは使用できません。おそらく SD カードが取り外されたか、破損しているか、あるいはファイルシステムが読み取り専用になっています。SD カードを確認するか、support@comaps.app までご連絡ください</string>
|
||||
<string name="setting_emulate_bad_storage">壊れたストレージをシミュレートする</string>
|
||||
<string name="core_entrance">入口</string>
|
||||
<string name="error_enter_correct_name">正しい名前を入力してください</string>
|
||||
@@ -648,7 +648,7 @@
|
||||
<string name="layers_title">マップのスタイルとレイヤー</string>
|
||||
<string name="subway_data_unavailable">地下鉄路線図はご利用いただけません</string>
|
||||
<string name="bookmarks_empty_list_title">このリストは空です</string>
|
||||
<string name="bookmarks_empty_list_message">ブックマークを追加するには、地図上の場所をタップし、星のアイコンをタップしてください。</string>
|
||||
<string name="bookmarks_empty_list_message">ブックマークを追加するには、地図上の場所をタップし、星のアイコンをタップしてください</string>
|
||||
<string name="category_desc_more">詳細</string>
|
||||
<string name="export_file">KMZをエクスポートする</string>
|
||||
<string name="export_file_gpx">GPXをエクスポートする</string>
|
||||
@@ -887,4 +887,6 @@
|
||||
<string name="tts_info_link">https://www.comaps.app/support/tts-configuration-guide-for-android/</string>
|
||||
<string name="app_site_url">https://comaps.app/</string>
|
||||
<string name="bluesky">Bluesky</string>
|
||||
<string name="pref_left_button_title">左ボタンの設定</string>
|
||||
<string name="pref_left_button_disable">無効にする</string>
|
||||
</resources>
|
||||
|
||||
@@ -1328,4 +1328,8 @@
|
||||
<string name="type.highway.ladder">階子</string>
|
||||
<string name="type.attraction.bumper_car">バンパーカー</string>
|
||||
<string name="type.sport.diving">飛込</string>
|
||||
<string name="type.recycling.cartons">紙箱</string>
|
||||
<string name="type.organic.only">有機</string>
|
||||
<string name="type.organic.yes">有機</string>
|
||||
<string name="type.power.pole">電柱</string>
|
||||
</resources>
|
||||
|
||||
@@ -36,11 +36,11 @@
|
||||
<string name="try_again">Tentar novamente</string>
|
||||
<string name="about_menu_title">Sobre o CoMaps</string>
|
||||
<!-- Text in About screen -->
|
||||
<string name="about_headline">Grátis para todos, feito com amor</string>
|
||||
<string name="about_headline">Projeto aberto impulsionado pela comunidade</string>
|
||||
<!-- Text in About screen -->
|
||||
<string name="about_proposition_1">• Sem anúncios, rastreamento ou coleta de dados</string>
|
||||
<string name="about_proposition_1">• Fácil de usar e elegante</string>
|
||||
<!-- Text in About screen -->
|
||||
<string name="about_proposition_2">• Sem consumo de bateria, funciona off-line</string>
|
||||
<string name="about_proposition_2">• Focado na privacidade e sem anúncios</string>
|
||||
<!-- The button that opens system location settings -->
|
||||
<string name="location_settings">Configurações de localização</string>
|
||||
<string name="close">Fechar</string>
|
||||
@@ -82,7 +82,7 @@
|
||||
<!-- Header of settings activity where user defines storage path -->
|
||||
<string name="maps_storage">Salvar mapas para</string>
|
||||
<!-- Detailed description of Maps Storage settings button -->
|
||||
<string name="maps_storage_summary">Selecione o local para onde os mapas devem ser baixados</string>
|
||||
<string name="maps_storage_summary">Selecione o local para onde os mapas devem ser baixados.</string>
|
||||
<!-- E.g. "Downloaded maps: 500Mb" in Maps Storage settings -->
|
||||
<string name="maps_storage_downloaded">Mapas</string>
|
||||
<!-- Internal storage type in Maps Storage settings (not accessible by the user) -->
|
||||
@@ -275,7 +275,7 @@
|
||||
<!-- Item in context menu. -->
|
||||
<string name="downloader_update_map">Atualizar mapa</string>
|
||||
<!-- Preference text -->
|
||||
<string name="pref_use_google_play">Determine rapidamente sua localização aproximada via Bluetooth, WiFi ou rede móvel</string>
|
||||
<string name="pref_use_google_play">Um serviço proprietário que combina GPS, Wi-Fi, redes móveis etc. para determinar sua localização com mais precisão. Ele pode se conectar aos servidores do Google.</string>
|
||||
<!-- Preference title -->
|
||||
<!-- Preference description -->
|
||||
<!-- Text for routing error dialog -->
|
||||
@@ -367,8 +367,8 @@
|
||||
<string name="read_in_wikipedia">Wikipédia</string>
|
||||
<string name="p2p_your_location">Localização atual</string>
|
||||
<string name="p2p_start">Iniciar</string>
|
||||
<string name="p2p_from_here">De</string>
|
||||
<string name="p2p_to_here">Para</string>
|
||||
<string name="p2p_from_here">Rota de</string>
|
||||
<string name="p2p_to_here">Rota para</string>
|
||||
<string name="p2p_only_from_current">Só é possível navegar a partir da sua localização atual.</string>
|
||||
<string name="p2p_reroute_from_current">Deseja planejar uma rota a partir da sua localização atual?</string>
|
||||
<!-- Edit open hours/set time and minutes dialog -->
|
||||
@@ -548,7 +548,7 @@
|
||||
<string name="bookmark_lists_show_all">Exibir tudo</string>
|
||||
<plurals name="bookmarks_places">
|
||||
<item quantity="one">%d favorito</item>
|
||||
<item quantity="many"/>
|
||||
<item quantity="many">%d favoritos</item>
|
||||
<item quantity="other">%d favoritos</item>
|
||||
</plurals>
|
||||
<string name="bookmarks_create_new_group">Criar nova lista</string>
|
||||
@@ -567,13 +567,13 @@
|
||||
<string name="profile">Perfil do OpenStreetMap</string>
|
||||
<plurals name="bookmarks_detect_message">
|
||||
<item quantity="one">%d arquivo foi encontrado. Você vai ver depois da conversão.</item>
|
||||
<item quantity="many"/>
|
||||
<item quantity="many">%d arquivos foram encontrados. Você os verá depois da conversão.</item>
|
||||
<item quantity="other">%d arquivos foram encontrados. Você os verá depois da conversão.</item>
|
||||
</plurals>
|
||||
<string name="restore">Restaurar</string>
|
||||
<plurals name="tracks">
|
||||
<item quantity="one">%d trilha</item>
|
||||
<item quantity="many"/>
|
||||
<item quantity="many">%d trilhas</item>
|
||||
<item quantity="other">%d trilhas</item>
|
||||
</plurals>
|
||||
<string name="privacy_policy">Política de privacidade</string>
|
||||
@@ -691,7 +691,7 @@
|
||||
<!-- A preference title; keep short! -->
|
||||
<string name="enable_show_on_lock_screen">Mostrar na tela de bloqueio</string>
|
||||
<!-- Description in preferences -->
|
||||
<string name="enable_show_on_lock_screen_description">Quando ativado, você não precisará debloquear seu dispositivo toda vez que o aplicativo estiver funcionando</string>
|
||||
<string name="enable_show_on_lock_screen_description">Quando ativado, você não precisará desbloquear seu dispositivo toda vez que o aplicativo estiver funcionando.</string>
|
||||
<!-- Current language of the map! -->
|
||||
<string name="change_map_locale">Idioma do mapa</string>
|
||||
<!-- OpenStreetMap text on splash screen -->
|
||||
@@ -771,16 +771,100 @@
|
||||
<!-- Message for the toast when saving the track recording is finished but nothing to save. -->
|
||||
<string name="track_recording_toast_nothing_to_save">A trilha está vazia - não há nada para salvar</string>
|
||||
<!-- Error message when there are no File Manager apps installed to select a folder when importing Bookmarks and Tracks -->
|
||||
<string name="error_no_file_manager_app">Não é possível exibir a caixa de diálogo de seleção de pastas porque nenhum aplicativo adequado está instalado no seu dispositivo. Instale um aplicativo gerenciador de arquivos e tente novamente</string>
|
||||
<string name="error_no_file_manager_app">Não é possível exibir a caixa de diálogo de seleção de pastas porque nenhum aplicativo adequado está instalado no seu dispositivo. Instale um aplicativo gerenciador de arquivos e tente novamente.</string>
|
||||
<string name="choose_color">Escolher cor</string>
|
||||
<string name="edit_track">Editar trilha</string>
|
||||
<string name="uri_open_location_failed">Nenhum aplicativo instalado que possa abrir o local</string>
|
||||
<!-- preference string for using auto theme only in navigation mode -->
|
||||
<string name="nav_auto">Automático na navegação</string>
|
||||
<string name="wikimedia_commons">Wikimedia Commons</string>
|
||||
<string name="about_proposition_3">• Rápido, minimalista, desenvolvido pela comunidade</string>
|
||||
<string name="about_proposition_3">• Não precisa de conexão à internet, rápido e leve</string>
|
||||
<string name="drive_through">Drive-through</string>
|
||||
<string name="editor_operator">Operador</string>
|
||||
<string name="mb">MB</string>
|
||||
<string name="gb">GB</string>
|
||||
<string name="editor_line_social_network">LINE</string>
|
||||
<string name="pref_map_style_title">Modo noturno</string>
|
||||
<string name="off">Desativado</string>
|
||||
<string name="on">Ativado</string>
|
||||
<string name="menu">Menu</string>
|
||||
<string name="news">Notícias</string>
|
||||
<string name="github">GitHub</string>
|
||||
<string name="telegram">Telegram</string>
|
||||
<string name="matrix">[Matrix]</string>
|
||||
<string name="about_developed_by_enthusiasts">Totalmente de código aberto, sem fins lucrativos, com processos de tomada de decisões e finanças transparentes.</string>
|
||||
<string name="limited_accuracy">Precisão limitada</string>
|
||||
<string name="pref_tts_street_names_title">Anunciar nomes de ruas</string>
|
||||
<string name="faq">Perguntas frequentes</string>
|
||||
<string name="bluesky">Bluesky</string>
|
||||
<string name="openstreetmap">OpenStreetMap</string>
|
||||
<string name="already_donated">Já doei</string>
|
||||
<string name="remind_me_later">Lembre-me mais tarde</string>
|
||||
<string name="mastodon">Mastodon</string>
|
||||
<string name="facebook">Facebook</string>
|
||||
<string name="twitter">X (Twitter)</string>
|
||||
<string name="instagram">Instagram</string>
|
||||
<string name="vk">VK</string>
|
||||
<string name="app_site_url">https://comaps.app/pt-BR/</string>
|
||||
<string name="dialog_routing_change_intermediate">Não foi possível localizar o ponto intermediário.</string>
|
||||
<string name="editor_time_from">De</string>
|
||||
<string name="how_to_support_us">Apoie o projeto</string>
|
||||
<string name="google_play_services">Serviço de localização integrada do Google</string>
|
||||
<string name="editor_time_title">Horário de funcionamento</string>
|
||||
<string name="support_organic_maps">Apoie o CoMaps</string>
|
||||
<string name="long_tap_toast">Dê um toque longo no mapa novamente para ver a interface</string>
|
||||
<string name="editor_time_add">Adicionar horário</string>
|
||||
<string name="editor_time_open">Aberto</string>
|
||||
<string name="editor_time_close">Fechado</string>
|
||||
<string name="editor_time_advanced">Modo avançado</string>
|
||||
<string name="editor_time_simple">Modo simples</string>
|
||||
<string name="editor_hours_closed">Horas sem funcionamento</string>
|
||||
<string name="editor_example_values">Valores de exemplo</string>
|
||||
<string name="editor_time_to">Para</string>
|
||||
<string name="layers_title">Estilos e camadas de mapas</string>
|
||||
<string name="export_file">Exportar KMZ</string>
|
||||
<string name="export_file_gpx">Exportar GPX</string>
|
||||
<string name="elevation_profile_descent">Descida</string>
|
||||
<string name="elevation_profile_difficulty">Dificuldade</string>
|
||||
<string name="osm_wiki_about_url">https://wiki.openstreetmap.org/wiki/Pt:Sobre_o_OpenStreetMap</string>
|
||||
<string name="editor_focus_map_on_location">Mova o mapa para colocar a cruz na localização da empresa ou do local.</string>
|
||||
<string name="pref_left_button_disable">Desabilitar</string>
|
||||
<string name="saved">Salvo</string>
|
||||
<string name="privacy">Privacidade</string>
|
||||
<string name="pref_tts_speedcams_auto">Avisar se exceder limite de velocidade</string>
|
||||
<string name="telegram_url">https://t.me/CoMapsApp/</string>
|
||||
<string name="elevation_profile_ascent">Subida</string>
|
||||
<string name="editor_other_info">Nota para os voluntários do OpenStreetMap (opcional)</string>
|
||||
<string name="building">Edifício</string>
|
||||
<string name="level">Andar</string>
|
||||
<string name="editor_default_language_hint">Como escrito na língua local</string>
|
||||
<string name="navigation_stop_button">Parar</string>
|
||||
<string name="network">Rede: %s</string>
|
||||
<string name="by_name">Por nome</string>
|
||||
<string name="comma_separated_pair">%1$s, %2$s</string>
|
||||
<string name="clear_the_search">Limpar a pesquisa</string>
|
||||
<string name="zoom_out">Diminuir o zoom</string>
|
||||
<string name="volume">Volume</string>
|
||||
<string name="pref_left_button_title">Configuração do botão esquerdo</string>
|
||||
<string name="error_enter_correct_facebook_page">Insira um endereço da web, conta ou nome de página válidos do Facebook</string>
|
||||
<string name="error_enter_correct_line_page">Insira um ID de LINE ou endereço da web válido</string>
|
||||
<string name="social_media">Redes sociais</string>
|
||||
<string name="tts_info_link">https://www.comaps.app/support/tts-configuration-guide-for-android/</string>
|
||||
<string name="editor_add_phone">Adicionar telefone</string>
|
||||
<string name="outdoor_seating">Assentos ao ar livre</string>
|
||||
<string name="capacity">Capacidade: %s</string>
|
||||
<string name="editor_correct_mistake">Corrigir erro</string>
|
||||
<string name="editor_add_select_location">Selecione a localização</string>
|
||||
<string name="not_signed_in">Não conectado</string>
|
||||
<string name="level_value_generic">Andar: %s</string>
|
||||
<string name="editor_note_hint">Descreva erros no mapa ou coisas que não podem ser editadas com CoMaps</string>
|
||||
<string name="operator">Operador: %s</string>
|
||||
<string name="error_enter_correct_instagram_page">Insira um nome de usuário válido do Instagram ou endereço da web</string>
|
||||
<string name="error_enter_correct_twitter_page">Digite um nome de usuário válido do Twitter ou endereço da web</string>
|
||||
<string name="error_enter_correct_vk_page">Digite um nome de usuário ou endereço da web válido do VK</string>
|
||||
<string name="error_enter_correct_fediverse_page">Digite um nome de usuário ou endereço da web válido do Mastodon</string>
|
||||
<string name="error_enter_correct_bluesky_page">Digite um nome de usuário ou endereço da web válido do Bluesky</string>
|
||||
<string name="prefs_languages_information_off">Para alguns idiomas, você precisará instalar um sintetizador de voz ou um pacote de idiomas adicional da loja de aplicativos (Google Play, Galaxy Store, App Gallery, FDroid).\nAbra as configurações do seu dispositivo → Idioma e entrada → Fala → Saída de texto para fala.\nAqui, você pode gerenciar as configurações de síntese de voz (por exemplo, baixar o pacote de idiomas para uso offline) e selecionar outro mecanismo de conversão de texto para fala.</string>
|
||||
<string name="codeberg">Codeberg</string>
|
||||
<string name="instagram_url">https://www.instagram.com/comapscommunity/</string>
|
||||
</resources>
|
||||
|
||||
@@ -887,4 +887,10 @@
|
||||
<string name="instagram_url">https://www.instagram.com/comapscommunity</string>
|
||||
<string name="codeberg">Codeberg</string>
|
||||
<string name="telegram_url">https://t.me/CoMapsApp/</string>
|
||||
<string name="pref_left_button_title">Sol alt tuş ataması</string>
|
||||
<string name="pref_left_button_disable">Devre dışı</string>
|
||||
<string name="error_enter_correct_fediverse_page">Gireceğiniz Mastodon kullanıcı adı ve adresi halihazırda mevcut olmalı</string>
|
||||
<string name="saved">Kaydedildi</string>
|
||||
<string name="bluesky">Bluesky</string>
|
||||
<string name="error_enter_correct_bluesky_page">Gireceğiniz Bluesky kullanıcı adı ve adresi halihazırda mevcut olmalı</string>
|
||||
</resources>
|
||||
|
||||
@@ -1367,4 +1367,7 @@
|
||||
<string name="type.fee.yes">₺</string>
|
||||
<string name="type.power.pole">Elektrik Direği</string>
|
||||
<string name="type.man_made.utility_pole">Hizmet Direği</string>
|
||||
<string name="type.amenity.studio">Stüdyo</string>
|
||||
<string name="type.sport.diving">Dalış Sporu</string>
|
||||
<string name="type.man_made.survey_point">Anket Noktası</string>
|
||||
</resources>
|
||||
|
||||
@@ -503,7 +503,7 @@
|
||||
<!-- Text in About and OSM Login screens. First %@ is replaced by a local, human readable date. -->
|
||||
<string name="osm_presentation">截至 %s 的社群創建的 OpenStreetMap 資料。請訪問 OpenStreetMap.org 以了解有關如何編輯和更新地圖的更多資訊</string>
|
||||
<!-- OSM explanation on Android login screen -->
|
||||
<string name="login_osm_presentation">OpenStreetMap.org(OSM)是一個建立免費開放地圖的社群專案。 它是CoMaps中地圖資料的主要來源,其工作原理類似於維基百科。 您可以新增或編輯地點,可供全世界數以百萬計的使用者使用並受益。\n 加入OSM,讓我們共繪更優質的地圖吧!</string>
|
||||
<string name="login_osm_presentation">OpenStreetMap.org(OSM)是一個建立免費開放地圖的社群專案。 它是 CoMaps 中地圖資料的主要來源,其工作原理類似於維基百科。 您可以新增或編輯地點,可供全世界數以百萬計的使用者使用並受益。\n 加入OSM,讓我們共繪更優質的地圖吧!</string>
|
||||
<string name="login_to_make_edits_visible">建立 OpenStreetMap 帳戶或登入以向全世界發布您的地圖編輯。</string>
|
||||
<!-- Downloaded 10 **of** 20 <- it is that "of" -->
|
||||
<string name="downloader_of">%1$d / %2$d</string>
|
||||
@@ -892,4 +892,6 @@
|
||||
<string name="error_enter_correct_fediverse_page">請輸入有效的 Mastodon 使用者名稱或網址</string>
|
||||
<string name="error_enter_correct_bluesky_page">請輸入有效的 Bluesky 使用者名稱或網址</string>
|
||||
<string name="bluesky">Bluesky</string>
|
||||
<string name="pref_left_button_disable">停用</string>
|
||||
<string name="pref_left_button_title">左側按鈕設定</string>
|
||||
</resources>
|
||||
|
||||
@@ -20,4 +20,16 @@
|
||||
<string name="type.tourism.information.visitor_centre">旅遊服務中心</string>
|
||||
<string name="type.man_made.silo">筒倉</string>
|
||||
<string name="type.tourism.viewpoint">觀光景點</string>
|
||||
<string name="type.highway.world_level">高速公路—世界級</string>
|
||||
<string name="type.organic.only">有機</string>
|
||||
<string name="type.recycling.cardboard">硬紙板</string>
|
||||
<string name="type.recycling.cans">金屬罐</string>
|
||||
<string name="type.recycling.shoes">鞋子</string>
|
||||
<string name="type.recycling.green_waste">綠色垃圾/有機廢棄物</string>
|
||||
<string name="type.recycling.cartons">紙盒</string>
|
||||
<string name="type.highway.world_towns_level">高速公路—鎮級</string>
|
||||
<string name="type.leisure.track.area">賽道</string>
|
||||
<string name="type.organic.yes">有機</string>
|
||||
<string name="type.power.pole">電線桿</string>
|
||||
<string name="type.amenity.shelter.lean_to">三面避難所</string>
|
||||
</resources>
|
||||
|
||||
@@ -503,7 +503,7 @@
|
||||
<!-- Text in About and OSM Login screens. First %@ is replaced by a local, human readable date. -->
|
||||
<string name="osm_presentation">截至 %s 的社区创建的 OpenStreetMap 数据。请访问 OpenStreetMap.org 以了解有关如何编辑和更新地图的信息</string>
|
||||
<!-- OSM explanation on Android login screen -->
|
||||
<string name="login_osm_presentation">OpenStreetMap.org(OSM)是一个构建免费开放地图的社区项目。它是CoMaps中地图数据的主要来源,其工作原理类似于维基百科。您可以添加或编辑地点,可供全世界数以百万计的用户使用并受益。\n加入OSM,让我们共绘更优质的地图吧!</string>
|
||||
<string name="login_osm_presentation">OpenStreetMap.org(OSM)是一个构建免费开放地图的社区项目。它是 CoMaps 中地图数据的主要来源,其工作原理类似于维基百科。您可以添加或编辑地点,可供全世界数以百万计的用户使用并受益。\n加入OSM,让我们共绘更优质的地图吧!</string>
|
||||
<string name="login_to_make_edits_visible">创建 OpenStreetMap 账号或登录,向全世界发布您编辑的地图。</string>
|
||||
<!-- Downloaded 10 **of** 20 <- it is that "of" -->
|
||||
<string name="downloader_of">%1$d / %2$d</string>
|
||||
@@ -892,4 +892,6 @@
|
||||
<string name="bluesky">Bluesky</string>
|
||||
<string name="error_enter_correct_bluesky_page">请输入有效的 Bluesky 用户名或网址</string>
|
||||
<string name="saved">已保存</string>
|
||||
<string name="pref_left_button_disable">禁用</string>
|
||||
<string name="pref_left_button_title">左侧按钮设置</string>
|
||||
</resources>
|
||||
|
||||
@@ -652,7 +652,7 @@
|
||||
<string name="type.leisure.swimming_pool">游泳池</string>
|
||||
<string name="type.leisure.swimming_pool.private">游泳池</string>
|
||||
<string name="type.leisure.track">賽道</string>
|
||||
<string name="type.leisure.track.area">賽道</string>
|
||||
<string name="type.leisure.track.area">赛道</string>
|
||||
<string name="type.leisure.water_park">水上乐园</string>
|
||||
<string name="type.leisure.beach_resort">海滨度假区</string>
|
||||
<string name="type.man_made">人造要素</string>
|
||||
|
||||
@@ -138,6 +138,8 @@
|
||||
<color name="elevation_profile">#1E96F0</color>
|
||||
<color name="elevation_profile_dark">#4BB9E6</color>
|
||||
|
||||
<color name="active_track_recording">#0057ff</color>
|
||||
|
||||
<color name="material_calendar_surface_dark">#929292</color>
|
||||
<color name="notification_warning">#FFC22219</color>
|
||||
</resources>
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
<string name="pref_keep_screen_on" translatable="false">KeepScreenOn</string>
|
||||
<string name="pref_show_on_lock_screen" translatable="false">ShowOnLockScreen</string>
|
||||
<string name="pref_map_locale" translatable="false">MapLanguage</string>
|
||||
<string name="pref_left_button" translatable="false">LeftButton</string>
|
||||
|
||||
<string name="notification_ticker_ltr" translatable="false">%1$s: %2$s</string>
|
||||
<string name="notification_ticker_rtl" translatable="false">%2$s :%1$s</string>
|
||||
|
||||
@@ -930,6 +930,6 @@
|
||||
<!-- preference string for using auto theme only in navigation mode -->
|
||||
<string name="nav_auto">Auto in navigation</string>
|
||||
<string name="codeberg">Codeberg</string>
|
||||
<string name="pref_universal_button">Universal button setup</string>
|
||||
<string name="pref_universal_button_not_display">Do not display</string>
|
||||
<string name="pref_left_button_title">Left button setup</string>
|
||||
<string name="pref_left_button_disable">Disable</string>
|
||||
</resources>
|
||||
|
||||
@@ -28,16 +28,22 @@
|
||||
app:singleLineTitle="false"
|
||||
android:summary="@string/pref_zoom_summary"
|
||||
android:order="2"/>
|
||||
<ListPreference
|
||||
android:key="@string/pref_left_button"
|
||||
android:title="@string/pref_left_button_title"
|
||||
app:singleLineTitle="false"
|
||||
android:persistent="true"
|
||||
android:order="3"/>
|
||||
<SwitchPreferenceCompat
|
||||
android:key="@string/pref_3d_buildings"
|
||||
android:title="@string/pref_map_3d_buildings_title"
|
||||
app:singleLineTitle="false"
|
||||
android:order="3"/>
|
||||
android:order="4"/>
|
||||
<SwitchPreferenceCompat
|
||||
android:key="@string/pref_autodownload"
|
||||
android:title="@string/autodownload"
|
||||
app:singleLineTitle="false"
|
||||
android:order="4"/>
|
||||
android:order="5"/>
|
||||
<SwitchPreferenceCompat
|
||||
android:key="@string/pref_large_fonts_size"
|
||||
android:title="@string/big_font"
|
||||
@@ -106,12 +112,6 @@
|
||||
app:singleLineTitle="false"
|
||||
android:persistent="false"
|
||||
android:order="18"/>
|
||||
<ListPreference
|
||||
android:key="universal_button"
|
||||
android:title="@string/pref_universal_button"
|
||||
app:singleLineTitle="false"
|
||||
android:persistent="true"
|
||||
android:order="20"/>
|
||||
</androidx.preference.PreferenceCategory>
|
||||
|
||||
<androidx.preference.PreferenceCategory
|
||||
|
||||
@@ -67,7 +67,11 @@ UNIT_TEST(AlmostEqualULPs_double)
|
||||
TEST(!base::AlmostEqualULPs(1.0, -1.0), ());
|
||||
TEST(!base::AlmostEqualULPs(2.0, -2.0), ());
|
||||
TEST(!base::AlmostEqualULPs(dmax, -dmax), ());
|
||||
|
||||
// That's why AlmostEqualULPs is a strange function, IMHO.
|
||||
TEST(!base::AlmostEqualULPs(0.0, eps), ());
|
||||
TEST(!base::AlmostEqualULPs(-eps, 0.0), ());
|
||||
TEST(!base::AlmostEqualULPs(eps, 2.0*eps), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(AlmostEqualULPs_float)
|
||||
@@ -91,7 +95,11 @@ UNIT_TEST(AlmostEqualULPs_float)
|
||||
TEST(!base::AlmostEqualULPs(1.0f, -1.0f), ());
|
||||
TEST(!base::AlmostEqualULPs(2.0f, -2.0f), ());
|
||||
TEST(!base::AlmostEqualULPs(dmax, -dmax), ());
|
||||
|
||||
// That's why AlmostEqualULPs is a strange function, IMHO.
|
||||
TEST(!base::AlmostEqualULPs(0.0f, eps), ());
|
||||
TEST(!base::AlmostEqualULPs(-eps, 0.0f), ());
|
||||
TEST(!base::AlmostEqualULPs(eps, 2.0f*eps), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(AlmostEqual_Smoke)
|
||||
|
||||
@@ -34,6 +34,8 @@ public:
|
||||
}
|
||||
|
||||
size_t size() const { return m_map.size(); }
|
||||
auto begin() const { return m_map.begin(); }
|
||||
auto end() const { return m_map.end(); }
|
||||
|
||||
protected:
|
||||
/// @todo buffer_vector is not suitable now, because Key/Value is not default constructible.
|
||||
|
||||
@@ -440,15 +440,66 @@ bool AlmostEqual(std::string const & str1, std::string const & str2, size_t mism
|
||||
return false;
|
||||
}
|
||||
|
||||
void ParseCSVRow(std::string const & s, char const delimiter, std::vector<std::string> & target)
|
||||
namespace
|
||||
{
|
||||
// Trim, unquote the string, and unescape two double quotes.
|
||||
std::string & UnescapeCSVColumn(std::string & s)
|
||||
{
|
||||
Trim(s);
|
||||
|
||||
if (s.size() < 2)
|
||||
return s;
|
||||
|
||||
if (*s.begin() == '"' && *s.rbegin() == '"')
|
||||
s = s.substr(1, s.size() - 2);
|
||||
|
||||
for (size_t i = 1; i < s.size(); ++i)
|
||||
if (s[i] == '"' && s[i - 1] == '"')
|
||||
s.erase(i, 1);
|
||||
|
||||
return s;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ParseCSVRow(std::string const & row, char const delimiter, std::vector<std::string> & target)
|
||||
{
|
||||
target.clear();
|
||||
TokenizeIterator<SimpleDelimiter, std::string::const_iterator, true /* KeepEmptyTokens */> it(s.begin(), s.end(), delimiter);
|
||||
for (; it; ++it)
|
||||
|
||||
std::string prevColumns;
|
||||
for (TokenizeIterator<SimpleDelimiter, std::string::const_iterator, true /* KeepEmptyTokens */> it {row.begin(), row.end(), delimiter}; it; ++it)
|
||||
{
|
||||
std::string column(*it);
|
||||
Trim(column);
|
||||
target.push_back(std::move(column));
|
||||
std::string_view column = *it;
|
||||
size_t const quotesCount = std::count(column.begin(), column.end(), '"');
|
||||
bool const evenQuotes = quotesCount % 2 == 0;
|
||||
if (prevColumns.empty())
|
||||
{
|
||||
if (evenQuotes)
|
||||
{
|
||||
if (quotesCount == 0)
|
||||
target.emplace_back(column);
|
||||
else
|
||||
{
|
||||
std::string strColumn {column};
|
||||
target.push_back(UnescapeCSVColumn(strColumn));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
prevColumns = column;
|
||||
prevColumns.push_back(',');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
prevColumns.append(column);
|
||||
if (evenQuotes)
|
||||
prevColumns.push_back(',');
|
||||
else
|
||||
{
|
||||
target.push_back(UnescapeCSVColumn(prevColumns));
|
||||
prevColumns.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Special case: if the string is empty, return an empty array instead of {""}.
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace csv_reader_test
|
||||
{
|
||||
using platform::tests_support::ScopedFile;
|
||||
|
||||
using Row = coding::CSVReader::Row;
|
||||
@@ -179,3 +181,41 @@ UNIT_TEST(CSVReaderIterator)
|
||||
TEST_EQUAL(index, answer.size(), ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(CSVReaderEmptyColumns)
|
||||
{
|
||||
auto const kContentWithEmptyColumns = ",,2,,4,\n,,,,,";
|
||||
auto const fileName = "test.csv";
|
||||
ScopedFile sf(fileName, kContentWithEmptyColumns);
|
||||
Rows const answer = {{"", "", "2", "", "4", ""}, {"", "", "", "", "", ""}};
|
||||
coding::CSVReader reader(sf.GetFullPath());
|
||||
size_t index = 0;
|
||||
while (auto const optionalRow = reader.ReadRow())
|
||||
{
|
||||
TEST_EQUAL(*optionalRow, answer[index], ());
|
||||
++index;
|
||||
}
|
||||
TEST_EQUAL(index, answer.size(), ());
|
||||
TEST(!reader.ReadRow(), ());
|
||||
TEST(!reader.ReadRow(), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(CSVReaderQuotes)
|
||||
{
|
||||
auto const kContentWithQuotes = R"(noquotes, "" , "with space","with, comma","""double"" quotes","""double,"", commas", """""",)";
|
||||
auto const fileName = "test.csv";
|
||||
ScopedFile sf(fileName, kContentWithQuotes);
|
||||
Rows const answer = {{"noquotes", "", "with space", "with, comma", "\"double\" quotes", "\"double,\", commas","\"\"", ""}};
|
||||
coding::CSVReader reader(sf.GetFullPath());
|
||||
size_t index = 0;
|
||||
while (auto const optionalRow = reader.ReadRow())
|
||||
{
|
||||
TEST_EQUAL(*optionalRow, answer[index], ());
|
||||
++index;
|
||||
}
|
||||
TEST_EQUAL(index, answer.size(), ());
|
||||
TEST(!reader.ReadRow(), ());
|
||||
TEST(!reader.ReadRow(), ());
|
||||
}
|
||||
|
||||
} // namespace csv_reader_test
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
#include "testing/testing.hpp"
|
||||
|
||||
#include "coding/internal/file_data.hpp"
|
||||
#include "coding/writer.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <cstring> // strlen
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace file_data_test
|
||||
{
|
||||
@@ -220,6 +217,35 @@ UNIT_TEST(EmptyFile)
|
||||
TEST(DeleteFileX(copy), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(RenameOnExistingFile)
|
||||
{
|
||||
using namespace base;
|
||||
|
||||
std::string const name = "test.empty";
|
||||
std::string const copy = "test.empty.copy";
|
||||
{
|
||||
FileData f(name, FileData::Op::WRITE_TRUNCATE);
|
||||
uint8_t const x = 1;
|
||||
f.Write(&x, 1);
|
||||
}
|
||||
{
|
||||
FileData f(copy, FileData::Op::WRITE_TRUNCATE);
|
||||
uint8_t const x = 2;
|
||||
f.Write(&x, 1);
|
||||
}
|
||||
|
||||
TEST(RenameFileX(name, copy), ());
|
||||
|
||||
{
|
||||
FileData f(copy, FileData::Op::READ);
|
||||
uint8_t x;
|
||||
f.Read(0, &x, 1);
|
||||
TEST_EQUAL(x, 1, ());
|
||||
}
|
||||
|
||||
TEST(DeleteFileX(copy), ());
|
||||
}
|
||||
|
||||
// Made this 'obvious' test for getline. I had (or not?) behaviour when 'while (getline)' loop
|
||||
// didn't get last string in file without trailing '\n'.
|
||||
UNIT_TEST(File_StdGetLine)
|
||||
|
||||
@@ -19,7 +19,7 @@ with open('countries-to-generate.json') as f1:
|
||||
entry = {
|
||||
"key": c,
|
||||
"value": {
|
||||
"profileName": "normal",
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": list(),
|
||||
"tilesAreBanned": False
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
{
|
||||
"key": "Algeria_Central",
|
||||
"value": {
|
||||
"profileName": "extra_small",
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
@@ -9691,6 +9691,262 @@
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Angola",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Australia_Northern Territory",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Australia_Queensland",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Australia_Western Australia",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Brazil_Mato Grosso",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Brazil_North Region_East",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Brazil_North Region_West",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Cameroon_Central",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Canada_Nunavut_North",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Canada_Nunavut_South",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "China_Gansu",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "China_Sichuan",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Colombia_East",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Congo-Kinshasa_West",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Indonesia_Central",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Indonesia_West",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Iran_East",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Kenya",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Libya",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Madagascar",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Malaysia",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Mali",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Mongolia",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Mozambique",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Myanmar",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Vietnam",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Nigeria_South",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Russia_Krasnoyarsk Krai_North",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Russia_Sakha Republic",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Tanzania",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Zambia",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Zimbabwe",
|
||||
"value": {
|
||||
"profileName": "poor",
|
||||
"tileCoordsSubset": [],
|
||||
"tilesAreBanned": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -63,13 +63,13 @@
|
||||
{
|
||||
"key": "poor",
|
||||
"value": {
|
||||
"alitudesStep": 500,
|
||||
"alitudesStep": 100,
|
||||
"gaussianFilterRFactor": 1.0,
|
||||
"gaussianFilterStDev": 2.0,
|
||||
"latLonStepFactor": 2,
|
||||
"latLonStepFactor": 4,
|
||||
"maxIsolinesLength": 500,
|
||||
"medianFilterR": 1,
|
||||
"simplificationZoom": 14
|
||||
"simplificationZoom": 13
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
4622
data/countries.txt
4622
data/countries.txt
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
#include "testing/testing.hpp"
|
||||
|
||||
#include "drape_frontend/path_text_handle.hpp"
|
||||
#include "drape_frontend/visual_params.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
@@ -15,10 +16,12 @@ bool IsSmooth(m2::SplineEx const & spline)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(Rounding_Spline)
|
||||
{
|
||||
df::VisualParams::Init(1.0, 1024);
|
||||
|
||||
m2::SplineEx spline1;
|
||||
df::AddPointAndRound(spline1, m2::PointD(0, 200));
|
||||
df::AddPointAndRound(spline1, m2::PointD(0, 0));
|
||||
|
||||
@@ -3,54 +3,47 @@
|
||||
|
||||
#include "routing/routing_helpers.hpp"
|
||||
|
||||
#include "indexer/altitude_loader.hpp"
|
||||
#include "indexer/feature.hpp"
|
||||
#include "indexer/feature_altitude.hpp"
|
||||
#include "indexer/feature_data.hpp"
|
||||
#include "indexer/feature_processor.hpp"
|
||||
|
||||
#include "coding/files_container.hpp"
|
||||
#include "coding/internal/file_data.hpp"
|
||||
#include "coding/read_write_utils.hpp"
|
||||
#include "coding/reader.hpp"
|
||||
#include "coding/succinct_mapper.hpp"
|
||||
#include "coding/varint.hpp"
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/checked_cast.hpp"
|
||||
#include "base/file_name_utils.hpp"
|
||||
#include "base/logging.hpp"
|
||||
#include "base/scope_guard.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include "defines.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "3party/succinct/elias_fano.hpp"
|
||||
#include "3party/succinct/mapper.hpp"
|
||||
#include "3party/succinct/rs_bit_vector.hpp"
|
||||
|
||||
namespace routing
|
||||
{
|
||||
using namespace feature;
|
||||
using namespace geometry;
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace routing;
|
||||
|
||||
class SrtmGetter : public AltitudeGetter
|
||||
{
|
||||
public:
|
||||
explicit SrtmGetter(std::string const & srtmDir) : m_srtmManager(srtmDir) {}
|
||||
|
||||
// AltitudeGetter overrides:
|
||||
geometry::Altitude GetAltitude(m2::PointD const & p) override
|
||||
Altitude GetAltitude(m2::PointD const & p) override
|
||||
{
|
||||
return m_srtmManager.GetHeight(mercator::ToLatLon(p));
|
||||
return m_srtmManager.GetAltitude(mercator::ToLatLon(p));
|
||||
}
|
||||
|
||||
void PrintStatsAndPurge() override
|
||||
{
|
||||
LOG(LINFO, ("Srtm tiles number (x26Mb):", m_srtmManager.GeTilesNumber()));
|
||||
m_srtmManager.Purge();
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -62,39 +55,24 @@ class Processor
|
||||
public:
|
||||
struct FeatureAltitude
|
||||
{
|
||||
FeatureAltitude() : m_featureId(0) {}
|
||||
FeatureAltitude(uint32_t featureId, Altitudes const & altitudes)
|
||||
: m_featureId(featureId), m_altitudes(altitudes)
|
||||
FeatureAltitude(uint32_t featureId, geometry::Altitudes && altitudes)
|
||||
: m_featureId(featureId), m_altitudes(std::move(altitudes))
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t m_featureId;
|
||||
Altitudes m_altitudes;
|
||||
feature::Altitudes m_altitudes;
|
||||
};
|
||||
|
||||
using TFeatureAltitudes = std::vector<FeatureAltitude>;
|
||||
|
||||
explicit Processor(AltitudeGetter & altitudeGetter)
|
||||
: m_altitudeGetter(altitudeGetter), m_minAltitude(geometry::kInvalidAltitude)
|
||||
: m_minAltitude(geometry::kInvalidAltitude), m_altitudeGetter(altitudeGetter)
|
||||
{
|
||||
}
|
||||
|
||||
TFeatureAltitudes const & GetFeatureAltitudes() const { return m_featureAltitudes; }
|
||||
|
||||
succinct::bit_vector_builder & GetAltitudeAvailabilityBuilder()
|
||||
void operator()(FeatureType & f, uint32_t id)
|
||||
{
|
||||
return m_altitudeAvailabilityBuilder;
|
||||
}
|
||||
|
||||
geometry::Altitude GetMinAltitude() const { return m_minAltitude; }
|
||||
|
||||
void operator()(FeatureType & f, uint32_t const & id)
|
||||
{
|
||||
if (id != m_altitudeAvailabilityBuilder.size())
|
||||
{
|
||||
LOG(LERROR, ("There's a gap in feature id order."));
|
||||
return;
|
||||
}
|
||||
CHECK_EQUAL(f.GetID().m_index, id, ());
|
||||
CHECK_EQUAL(id, m_altitudeAvailabilityBuilder.size(), ());
|
||||
|
||||
bool hasAltitude = false;
|
||||
SCOPE_GUARD(altitudeAvailabilityBuilding,
|
||||
@@ -109,12 +87,19 @@ public:
|
||||
return;
|
||||
|
||||
geometry::Altitudes altitudes;
|
||||
geometry::Altitude minFeatureAltitude = geometry::kInvalidAltitude;
|
||||
altitudes.reserve(pointsCount);
|
||||
Altitude minFeatureAltitude = geometry::kInvalidAltitude;
|
||||
for (size_t i = 0; i < pointsCount; ++i)
|
||||
{
|
||||
geometry::Altitude const a = m_altitudeGetter.GetAltitude(f.GetPoint(i));
|
||||
auto const & pt = f.GetPoint(i);
|
||||
Altitude const a = m_altitudeGetter.GetAltitude(pt);
|
||||
if (a == geometry::kInvalidAltitude)
|
||||
{
|
||||
// Print warning for missing altitude point (if not a ferry or so).
|
||||
auto const type = CarModel::AllLimitsInstance().GetHighwayType(feature::TypesHolder(f));
|
||||
if (type && *type != HighwayType::RouteFerry && *type != HighwayType::RouteShuttleTrain)
|
||||
LOG(LWARNING, ("Invalid altitude at:", mercator::ToLatLon(pt)));
|
||||
|
||||
// One invalid point invalidates the whole feature.
|
||||
return;
|
||||
}
|
||||
@@ -128,7 +113,7 @@ public:
|
||||
}
|
||||
|
||||
hasAltitude = true;
|
||||
m_featureAltitudes.emplace_back(id, Altitudes(std::move(altitudes)));
|
||||
m_featureAltitudes.emplace_back(id, std::move(altitudes));
|
||||
|
||||
if (m_minAltitude == geometry::kInvalidAltitude)
|
||||
m_minAltitude = minFeatureAltitude;
|
||||
@@ -138,22 +123,15 @@ public:
|
||||
|
||||
bool HasAltitudeInfo() const { return !m_featureAltitudes.empty(); }
|
||||
|
||||
bool IsFeatureAltitudesSorted()
|
||||
{
|
||||
return std::is_sorted(m_featureAltitudes.begin(), m_featureAltitudes.end(),
|
||||
base::LessBy(&Processor::FeatureAltitude::m_featureId));
|
||||
}
|
||||
|
||||
private:
|
||||
AltitudeGetter & m_altitudeGetter;
|
||||
TFeatureAltitudes m_featureAltitudes;
|
||||
public:
|
||||
std::vector<FeatureAltitude> m_featureAltitudes;
|
||||
succinct::bit_vector_builder m_altitudeAvailabilityBuilder;
|
||||
geometry::Altitude m_minAltitude;
|
||||
Altitude m_minAltitude;
|
||||
|
||||
AltitudeGetter & m_altitudeGetter;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace routing
|
||||
{
|
||||
void BuildRoadAltitudes(std::string const & mwmPath, AltitudeGetter & altitudeGetter)
|
||||
{
|
||||
try
|
||||
@@ -161,28 +139,27 @@ void BuildRoadAltitudes(std::string const & mwmPath, AltitudeGetter & altitudeGe
|
||||
// Preparing altitude information.
|
||||
Processor processor(altitudeGetter);
|
||||
feature::ForEachFeature(mwmPath, processor);
|
||||
processor.m_altitudeGetter.PrintStatsAndPurge();
|
||||
|
||||
if (!processor.HasAltitudeInfo())
|
||||
{
|
||||
LOG(LINFO, ("No altitude information for road features of mwm:", mwmPath));
|
||||
// Possible for small islands like Bouvet or Willis.
|
||||
LOG(LWARNING, ("No altitude information for road features of mwm:", mwmPath));
|
||||
return;
|
||||
}
|
||||
|
||||
CHECK(processor.IsFeatureAltitudesSorted(), ());
|
||||
|
||||
FilesContainerW cont(mwmPath, FileWriter::OP_WRITE_EXISTING);
|
||||
auto w = cont.GetWriter(ALTITUDES_FILE_TAG);
|
||||
|
||||
AltitudeHeader header;
|
||||
header.m_minAltitude = processor.GetMinAltitude();
|
||||
header.m_minAltitude = processor.m_minAltitude;
|
||||
|
||||
auto const startOffset = w->Pos();
|
||||
header.Serialize(*w);
|
||||
{
|
||||
// Altitude availability serialization.
|
||||
coding::FreezeVisitor<Writer> visitor(*w);
|
||||
succinct::bit_vector_builder & builder = processor.GetAltitudeAvailabilityBuilder();
|
||||
succinct::rs_bit_vector(&builder).map(visitor);
|
||||
succinct::rs_bit_vector(&processor.m_altitudeAvailabilityBuilder).map(visitor);
|
||||
}
|
||||
header.m_featureTableOffset = base::checked_cast<uint32_t>(w->Pos() - startOffset);
|
||||
|
||||
@@ -191,8 +168,7 @@ void BuildRoadAltitudes(std::string const & mwmPath, AltitudeGetter & altitudeGe
|
||||
{
|
||||
// Altitude info serialization to memory.
|
||||
MemWriter<std::vector<uint8_t>> writer(deltas);
|
||||
Processor::TFeatureAltitudes const & featureAltitudes = processor.GetFeatureAltitudes();
|
||||
for (auto const & a : featureAltitudes)
|
||||
for (auto const & a : processor.m_featureAltitudes)
|
||||
{
|
||||
offsets.push_back(base::checked_cast<uint32_t>(writer.Pos()));
|
||||
a.m_altitudes.Serialize(header.m_minAltitude, writer);
|
||||
@@ -200,8 +176,7 @@ void BuildRoadAltitudes(std::string const & mwmPath, AltitudeGetter & altitudeGe
|
||||
}
|
||||
{
|
||||
// Altitude offsets serialization.
|
||||
CHECK(std::is_sorted(offsets.begin(), offsets.end()), ());
|
||||
CHECK(adjacent_find(offsets.begin(), offsets.end()) == offsets.end(), ());
|
||||
CHECK(base::IsSortedAndUnique(offsets.begin(), offsets.end()), ());
|
||||
|
||||
succinct::elias_fano::elias_fano_builder builder(offsets.back(), offsets.size());
|
||||
for (uint32_t offset : offsets)
|
||||
@@ -221,9 +196,10 @@ void BuildRoadAltitudes(std::string const & mwmPath, AltitudeGetter & altitudeGe
|
||||
w->Seek(startOffset);
|
||||
header.Serialize(w);
|
||||
w->Seek(endOffset);
|
||||
|
||||
LOG(LINFO, (ALTITUDES_FILE_TAG, "section is ready. The size is", header.m_endOffset));
|
||||
if (processor.HasAltitudeInfo())
|
||||
LOG(LINFO, ("Min altitude is", processor.GetMinAltitude()));
|
||||
LOG(LINFO, ("Min altitude is", processor.m_minAltitude));
|
||||
else
|
||||
LOG(LINFO, ("Min altitude isn't defined."));
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include "indexer/feature_altitude.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace routing
|
||||
@@ -13,6 +11,7 @@ class AltitudeGetter
|
||||
{
|
||||
public:
|
||||
virtual geometry::Altitude GetAltitude(m2::PointD const & p) = 0;
|
||||
virtual void PrintStatsAndPurge() {}
|
||||
};
|
||||
|
||||
/// \brief Adds altitude section to mwm. It has the following format:
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "coding/varint.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/parametrized_segment.hpp"
|
||||
#include "geometry/simplification.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
@@ -24,20 +23,18 @@
|
||||
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include "defines.hpp"
|
||||
|
||||
namespace borders
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
template <class ToDo>
|
||||
void ForEachCountry(std::string const & baseDir, ToDo && toDo)
|
||||
{
|
||||
@@ -49,11 +46,11 @@ void ForEachCountry(std::string const & baseDir, ToDo && toDo)
|
||||
Platform::GetFilesByExt(bordersDir, BORDERS_EXTENSION, files);
|
||||
for (std::string file : files)
|
||||
{
|
||||
std::vector<m2::RegionD> polygons;
|
||||
PolygonsList polygons;
|
||||
if (LoadBorders(bordersDir + file, polygons))
|
||||
{
|
||||
base::GetNameWithoutExt(file);
|
||||
toDo(file, polygons);
|
||||
toDo(std::move(file), std::move(polygons));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,7 +62,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(std::string const & name, std::vector<m2::RegionD> const & borders)
|
||||
void operator()(std::string name, PolygonsList && borders)
|
||||
{
|
||||
// use index in vector as tag
|
||||
auto w = m_writer.GetWriter(strings::to_string(m_polys.size()));
|
||||
@@ -105,7 +102,7 @@ private:
|
||||
std::vector<storage::CountryDef> m_polys;
|
||||
};
|
||||
|
||||
bool ReadPolygon(std::istream & stream, m2::RegionD & region, std::string const & filename)
|
||||
bool ReadPolygon(std::istream & stream, Polygon & poly, std::string const & filename)
|
||||
{
|
||||
std::string line, name;
|
||||
double lon, lat;
|
||||
@@ -130,7 +127,7 @@ bool ReadPolygon(std::istream & stream, m2::RegionD & region, std::string const
|
||||
iss >> lon >> lat;
|
||||
CHECK(!iss.fail(), ("Incorrect data in", filename));
|
||||
|
||||
region.AddPoint(mercator::FromLatLon(lat, lon));
|
||||
poly.AddPoint(mercator::FromLatLon(lat, lon));
|
||||
}
|
||||
|
||||
// drop inner rings
|
||||
@@ -146,7 +143,7 @@ bool CountryPolygons::Contains(m2::PointD const & point) const
|
||||
});
|
||||
}
|
||||
|
||||
bool LoadBorders(std::string const & borderFile, std::vector<m2::RegionD> & outBorders)
|
||||
bool LoadBorders(std::string const & borderFile, PolygonsList & outBorders)
|
||||
{
|
||||
std::ifstream stream(borderFile);
|
||||
std::string line;
|
||||
@@ -156,12 +153,12 @@ bool LoadBorders(std::string const & borderFile, std::vector<m2::RegionD> & outB
|
||||
return false;
|
||||
}
|
||||
|
||||
m2::RegionD currentPolygon;
|
||||
Polygon currentPolygon;
|
||||
while (ReadPolygon(stream, currentPolygon, borderFile))
|
||||
{
|
||||
CHECK(currentPolygon.IsValid(), ("Invalid region in", borderFile));
|
||||
outBorders.emplace_back(std::move(currentPolygon));
|
||||
currentPolygon = m2::RegionD();
|
||||
currentPolygon = {};
|
||||
}
|
||||
|
||||
CHECK(!outBorders.empty(), ("No borders were loaded from", borderFile));
|
||||
@@ -178,7 +175,7 @@ bool GetBordersRect(std::string const & baseDir, std::string const & country,
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<m2::RegionD> borders;
|
||||
PolygonsList borders;
|
||||
CHECK(LoadBorders(bordersFile, borders), ());
|
||||
bordersRect.MakeEmpty();
|
||||
for (auto const & border : borders)
|
||||
@@ -192,13 +189,16 @@ CountryPolygonsCollection LoadCountriesList(std::string const & baseDir)
|
||||
LOG(LINFO, ("Loading countries in", BORDERS_DIR, "folder in", baseDir));
|
||||
|
||||
CountryPolygonsCollection countryPolygonsCollection;
|
||||
ForEachCountry(baseDir, [&](auto const & name, auto const & borders)
|
||||
ForEachCountry(baseDir, [&](std::string name, PolygonsList && borders)
|
||||
{
|
||||
PolygonsTree polygons;
|
||||
for (m2::RegionD const & border : borders)
|
||||
polygons.Add(border, border.GetRect());
|
||||
for (Polygon & border : borders)
|
||||
{
|
||||
auto const rect = border.GetRect();
|
||||
polygons.Add(std::move(border), rect);
|
||||
}
|
||||
|
||||
countryPolygonsCollection.Add(CountryPolygons(name, polygons));
|
||||
countryPolygonsCollection.Add(CountryPolygons(std::move(name), std::move(polygons)));
|
||||
});
|
||||
|
||||
LOG(LINFO, ("Countries loaded:", countryPolygonsCollection.GetSize()));
|
||||
@@ -214,7 +214,7 @@ void GeneratePackedBorders(std::string const & baseDir)
|
||||
}
|
||||
|
||||
void DumpBorderToPolyFile(std::string const & targetDir, storage::CountryId const & mwmName,
|
||||
std::vector<m2::RegionD> const & polygons)
|
||||
PolygonsList const & polygons)
|
||||
{
|
||||
CHECK(!polygons.empty(), ());
|
||||
|
||||
@@ -222,7 +222,8 @@ void DumpBorderToPolyFile(std::string const & targetDir, storage::CountryId cons
|
||||
std::ofstream poly(filePath);
|
||||
CHECK(poly.good(), ());
|
||||
|
||||
poly << std::setprecision(20) << std::fixed;
|
||||
// Used to have fixed precicion with 6 digits. And Alaska has 4 digits after comma :) Strange, but as is.
|
||||
poly << std::setprecision(6) << std::fixed;
|
||||
|
||||
poly << mwmName << std::endl;
|
||||
size_t polygonId = 1;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "generator/feature_builder.hpp"
|
||||
|
||||
#include "storage/storage_defines.hpp"
|
||||
|
||||
#include "coding/geometry_coding.hpp"
|
||||
@@ -11,12 +9,9 @@
|
||||
#include "geometry/region2d.hpp"
|
||||
#include "geometry/tree4d.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#define BORDERS_DIR "borders/"
|
||||
@@ -48,9 +43,8 @@ class CountryPolygons
|
||||
{
|
||||
public:
|
||||
CountryPolygons() = default;
|
||||
explicit CountryPolygons(std::string const & name, PolygonsTree const & regions)
|
||||
: m_name(name)
|
||||
, m_polygons(regions)
|
||||
explicit CountryPolygons(std::string && name, PolygonsTree && regions)
|
||||
: m_name(std::move(name)), m_polygons(std::move(regions))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -97,6 +91,8 @@ public:
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
|
||||
/// @todo Is it an overkill to store Tree4D for each country's polygon?
|
||||
PolygonsTree m_polygons;
|
||||
};
|
||||
|
||||
@@ -105,11 +101,15 @@ class CountryPolygonsCollection
|
||||
public:
|
||||
CountryPolygonsCollection() = default;
|
||||
|
||||
void Add(CountryPolygons const & countryPolygons)
|
||||
void Add(CountryPolygons && countryPolygons)
|
||||
{
|
||||
auto const it = m_countryPolygonsMap.emplace(countryPolygons.GetName(), countryPolygons);
|
||||
countryPolygons.ForEachPolygon([&](auto const & polygon) {
|
||||
m_regionsTree.Add(it.first->second, polygon.GetRect());
|
||||
auto const res = m_countryPolygonsMap.emplace(countryPolygons.GetName(), std::move(countryPolygons));
|
||||
CHECK(res.second, ());
|
||||
|
||||
auto const & inserted = res.first->second;
|
||||
inserted.ForEachPolygon([&inserted, this](Polygon const & polygon)
|
||||
{
|
||||
m_regionsTree.Add(inserted, polygon.GetRect());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -119,9 +119,10 @@ public:
|
||||
void ForEachCountryInRect(m2::RectD const & rect, ToDo && toDo) const
|
||||
{
|
||||
std::unordered_set<CountryPolygons const *> uniq;
|
||||
m_regionsTree.ForEachInRect(rect, [&](auto const & countryPolygons) {
|
||||
if (uniq.emplace(&countryPolygons.get()).second)
|
||||
toDo(countryPolygons);
|
||||
m_regionsTree.ForEachInRect(rect, [&](CountryPolygons const & cp)
|
||||
{
|
||||
if (uniq.insert(&cp).second)
|
||||
toDo(cp);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -142,8 +143,10 @@ private:
|
||||
std::unordered_map<std::string, CountryPolygons> m_countryPolygonsMap;
|
||||
};
|
||||
|
||||
using PolygonsList = std::vector<Polygon>;
|
||||
|
||||
/// @return false if borderFile can't be opened
|
||||
bool LoadBorders(std::string const & borderFile, std::vector<m2::RegionD> & outBorders);
|
||||
bool LoadBorders(std::string const & borderFile, PolygonsList & outBorders);
|
||||
|
||||
bool GetBordersRect(std::string const & baseDir, std::string const & country,
|
||||
m2::RectD & bordersRect);
|
||||
@@ -153,10 +156,10 @@ bool LoadCountriesList(std::string const & baseDir, CountryPolygonsCollection &
|
||||
void GeneratePackedBorders(std::string const & baseDir);
|
||||
|
||||
template <typename Source>
|
||||
std::vector<m2::RegionD> ReadPolygonsOfOneBorder(Source & src)
|
||||
PolygonsList ReadPolygonsOfOneBorder(Source & src)
|
||||
{
|
||||
auto const count = ReadVarUint<uint32_t>(src);
|
||||
std::vector<m2::RegionD> result(count);
|
||||
PolygonsList result(count);
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
std::vector<m2::PointD> points;
|
||||
@@ -168,7 +171,7 @@ std::vector<m2::RegionD> ReadPolygonsOfOneBorder(Source & src)
|
||||
}
|
||||
|
||||
void DumpBorderToPolyFile(std::string const & filePath, storage::CountryId const & mwmName,
|
||||
std::vector<m2::RegionD> const & polygons);
|
||||
PolygonsList const & polygons);
|
||||
void UnpackBorders(std::string const & baseDir, std::string const & targetDir);
|
||||
|
||||
CountryPolygonsCollection const & GetOrCreateCountryPolygonsTree(std::string const & baseDir);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "generator/utils.hpp"
|
||||
|
||||
#include "indexer/feature.hpp"
|
||||
#include "indexer/ftypes_matcher.hpp"
|
||||
#include "indexer/feature_processor.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
@@ -88,45 +87,38 @@ std::string DescriptionsCollectionBuilderStat::LangStatisticsToString() const
|
||||
|
||||
void DescriptionsCollector::operator() (FeatureType & ft, uint32_t featureId)
|
||||
{
|
||||
auto const & attractionsChecker = ftypes::AttractionsChecker::Instance();
|
||||
if (!attractionsChecker(ft))
|
||||
return;
|
||||
// auto const & attractionsChecker = ftypes::AttractionsChecker::Instance();
|
||||
// if (!attractionsChecker(ft))
|
||||
// return;
|
||||
|
||||
(*this)(ft.GetMetadata().GetWikiURL(), featureId);
|
||||
}
|
||||
|
||||
void DescriptionsCollector::operator() (std::string const & wikiUrl, uint32_t featureId)
|
||||
{
|
||||
std::string path;
|
||||
descriptions::LangMeta langsMeta;
|
||||
|
||||
size_t size = 0;
|
||||
// First try to get wikipedia url.
|
||||
bool const isWikiUrl = !wikiUrl.empty();
|
||||
if (isWikiUrl)
|
||||
if (!wikiUrl.empty())
|
||||
size = FindPageAndFill(MakePathForWikipedia(m_wikipediaDir, wikiUrl), langsMeta);
|
||||
|
||||
// Second try to get wikidata id.
|
||||
bool const isWikiUrl = !langsMeta.empty();
|
||||
if (!isWikiUrl)
|
||||
{
|
||||
path = MakePathForWikipedia(m_wikipediaDir, wikiUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Second try to get wikidata id.
|
||||
auto const wikidataId = m_wikidataHelper.GetWikidataId(featureId);
|
||||
if (wikidataId)
|
||||
path = MakePathForWikidata(m_wikipediaDir, *wikidataId);
|
||||
size = FindPageAndFill(MakePathForWikidata(m_wikipediaDir, *wikidataId), langsMeta);
|
||||
}
|
||||
|
||||
if (path.empty())
|
||||
if (langsMeta.empty())
|
||||
return;
|
||||
|
||||
descriptions::LangMeta langsMeta;
|
||||
int const sz = FindPageAndFill(path, langsMeta);
|
||||
if (sz < 0)
|
||||
{
|
||||
LOG(LWARNING, ("Page", path, "not found."));
|
||||
return;
|
||||
}
|
||||
else if (sz > 0)
|
||||
if (size > 0)
|
||||
{
|
||||
// Add only new loaded pages (not from cache).
|
||||
m_stat.AddSize(sz);
|
||||
m_stat.AddSize(size);
|
||||
m_stat.IncPage();
|
||||
}
|
||||
|
||||
@@ -166,10 +158,10 @@ std::string DescriptionsCollector::FillStringFromFile(std::string const & fullPa
|
||||
return std::string(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
int DescriptionsCollector::FindPageAndFill(std::string const & path, descriptions::LangMeta & meta)
|
||||
size_t DescriptionsCollector::FindPageAndFill(std::string const & path, descriptions::LangMeta & meta)
|
||||
{
|
||||
int size = -1;
|
||||
if (!IsValidDir(path))
|
||||
size_t size = 0;
|
||||
if (path.empty() || !IsValidDir(path))
|
||||
return size;
|
||||
|
||||
Platform::FilesList filelist;
|
||||
@@ -184,24 +176,27 @@ int DescriptionsCollector::FindPageAndFill(std::string const & path, description
|
||||
continue;
|
||||
}
|
||||
|
||||
if (size < 0)
|
||||
size = 0;
|
||||
|
||||
m_stat.IncCode(code);
|
||||
|
||||
auto res = m_path2Index.try_emplace(base::JoinPath(path, filename), 0);
|
||||
if (res.second)
|
||||
{
|
||||
auto const & filePath = res.first->first;
|
||||
auto content = FillStringFromFile(filePath);
|
||||
size_t const sz = content.size();
|
||||
if (sz == 0)
|
||||
{
|
||||
LOG(LWARNING, ("Empty descriptions file:", filePath));
|
||||
m_path2Index.erase(res.first);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto & strings = m_collection.m_strings;
|
||||
res.first->second = strings.size();
|
||||
strings.push_back(FillStringFromFile(filePath));
|
||||
strings.push_back(std::move(content));
|
||||
|
||||
size_t const sz = strings.back().size();
|
||||
CHECK(sz > 0, ("Empty file:", filePath));
|
||||
size += sz;
|
||||
}
|
||||
|
||||
m_stat.IncCode(code);
|
||||
meta.emplace_back(code, res.first->second);
|
||||
}
|
||||
|
||||
|
||||
@@ -78,8 +78,8 @@ public:
|
||||
|
||||
static std::string FillStringFromFile(std::string const & fullPath);
|
||||
|
||||
/// @return -1 If page not found. 0 if page from cache. Size > 0 if page was loaded from disk.
|
||||
int FindPageAndFill(std::string const & wikipediaUrl, descriptions::LangMeta & meta);
|
||||
/// @return Aggregated loaded from disk page's size.
|
||||
size_t FindPageAndFill(std::string const & wikipediaUrl, descriptions::LangMeta & meta);
|
||||
|
||||
public:
|
||||
DescriptionsCollectionBuilderStat m_stat;
|
||||
|
||||
@@ -188,7 +188,7 @@ public:
|
||||
for (uint32_t i = 0; i < numPoints; ++i)
|
||||
{
|
||||
// Feature segment altitude.
|
||||
geometry::Altitude altitude = m_srtmManager.GetHeight(mercator::ToLatLon(f.GetPoint(i)));
|
||||
geometry::Altitude altitude = m_srtmManager.GetAltitude(mercator::ToLatLon(f.GetPoint(i)));
|
||||
pointAltitudes[i] = altitude == geometry::kInvalidAltitude ? 0 : altitude;
|
||||
if (i == 0)
|
||||
{
|
||||
|
||||
@@ -113,8 +113,10 @@ public:
|
||||
StringUtf8Multilang str;
|
||||
std::string const badUrl = "https://en.wikipedia.org/wiki/Not_exists";
|
||||
auto const path = DescriptionsCollector::MakePathForWikipedia(m_wikiDir, badUrl);
|
||||
|
||||
descriptions::LangMeta meta;
|
||||
TEST_EQUAL(collector.FindPageAndFill(path, meta), -1, ());
|
||||
TEST_EQUAL(collector.FindPageAndFill(path, meta), 0, ());
|
||||
TEST(meta.empty(), ());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,26 @@
|
||||
|
||||
#include "generator/srtm_parser.hpp"
|
||||
|
||||
using namespace generator;
|
||||
#include "coding/endianness.hpp"
|
||||
|
||||
namespace
|
||||
#include <iostream>
|
||||
|
||||
namespace srtm_parser_test
|
||||
{
|
||||
inline std::string GetBase(ms::LatLon const & coord) { return SrtmTile::GetBase(coord); }
|
||||
using namespace generator;
|
||||
using namespace geometry;
|
||||
|
||||
UNIT_TEST(FilenameTests)
|
||||
inline std::string GetBase(ms::LatLon const & coord)
|
||||
{
|
||||
return SrtmTile::GetBase(coord);
|
||||
}
|
||||
|
||||
inline SrtmTile::LatLonKey GetKey(ms::LatLon const & coord)
|
||||
{
|
||||
return SrtmTile::GetKey(coord);
|
||||
}
|
||||
|
||||
UNIT_TEST(SRTM_FilenameTest)
|
||||
{
|
||||
auto name = GetBase({56.4566, 37.3467});
|
||||
TEST_EQUAL(name, "N56E037", ());
|
||||
@@ -16,13 +29,110 @@ UNIT_TEST(FilenameTests)
|
||||
name = GetBase({34.077433, -118.304569});
|
||||
TEST_EQUAL(name, "N34W119", ());
|
||||
|
||||
name = GetBase({1.0, 1.0});
|
||||
TEST_EQUAL(name, "N01E001", ());
|
||||
|
||||
name = GetBase({0.1, 0.1});
|
||||
TEST_EQUAL(name, "N00E000", ());
|
||||
|
||||
TEST_NOT_EQUAL(GetKey({0.1, 0.1}), GetKey({1.0, 1.0}), ());
|
||||
|
||||
name = GetBase({-0.1, -0.1});
|
||||
TEST_EQUAL(name, "S01W001", ());
|
||||
|
||||
TEST_NOT_EQUAL(GetKey({0.1, 0.1}), GetKey({-0.1, -0.1}), ());
|
||||
|
||||
name = GetBase({-0.9, -0.9});
|
||||
TEST_EQUAL(name, "S01W001", ());
|
||||
|
||||
TEST_EQUAL(GetKey({-0.9, -0.9}), GetKey({-0.1, -0.1}), ());
|
||||
|
||||
name = GetBase({-1.0, -1.0});
|
||||
TEST_EQUAL(name, "S01W001", ());
|
||||
|
||||
TEST_EQUAL(GetKey({-0.9, -0.9}), GetKey({-1.0, -1.0}), ());
|
||||
|
||||
name = GetBase({-1.9, -1.1});
|
||||
TEST_EQUAL(name, "S02W002", ());
|
||||
|
||||
TEST_NOT_EQUAL(GetKey({-1.1, -1.1}), GetKey({-1.0, -1.0}), ());
|
||||
|
||||
name = GetBase({-35.35, -12.1});
|
||||
TEST_EQUAL(name, "S36W013", ());
|
||||
|
||||
name = GetBase({-34.622358, -58.383654});
|
||||
TEST_EQUAL(name, "S35W059", ());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(SRTM_TileTest)
|
||||
{
|
||||
SrtmTile tile;
|
||||
size_t sz;
|
||||
Altitude * data = tile.DataForTests(sz);
|
||||
|
||||
// Fill 5 last rows:
|
||||
// -4 -4 -4
|
||||
// -2 -2 -2
|
||||
// 0 0 0
|
||||
// 2 2 2
|
||||
// 4 4 4
|
||||
size_t row = sz - 1;
|
||||
for (Altitude a = 4; a >= -4; a -= 2)
|
||||
{
|
||||
for (size_t i = row * sz; i < (row + 1) * sz; ++i)
|
||||
data[i] = ReverseByteOrder(a);
|
||||
--row;
|
||||
}
|
||||
|
||||
double const len = 1.0 / (sz - 1);
|
||||
|
||||
TEST_EQUAL(tile.GetHeight({0, 0}), 4, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(tile.GetTriangleHeight({0, 0}), 4.0, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(tile.GetBilinearHeight({0, 0}), 4.0, ());
|
||||
TEST_EQUAL(tile.GetHeight({len, len}), 2, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(tile.GetTriangleHeight({len, len}), 2.0, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(tile.GetBilinearHeight({len, len}), 2.0, ());
|
||||
|
||||
double l = len / 2;
|
||||
Altitude h = tile.GetHeight({l, l});
|
||||
TEST(h == 4 || h == 2, (h));
|
||||
TEST_ALMOST_EQUAL_ULPS(tile.GetTriangleHeight({l, l}), 3.0, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(tile.GetBilinearHeight({l, l}), 3.0, ());
|
||||
|
||||
l = 3 * len + len / 2;
|
||||
h = tile.GetHeight({l, l});
|
||||
TEST(h == -4 || h == -2, (h));
|
||||
TEST_ALMOST_EQUAL_ULPS(tile.GetTriangleHeight({l, l}), -3.0, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(tile.GetBilinearHeight({l, l}), -3.0, ());
|
||||
}
|
||||
|
||||
/*
|
||||
UNIT_TEST(SRTM_SamplesTest)
|
||||
{
|
||||
SrtmTileManager manager("/Users/vng/SRTM");
|
||||
|
||||
std::initializer_list<ms::LatLon> arr[] = {
|
||||
{{ 41.899802800880578957, 12.498703841110341273}, { 41.899748897914214751, 12.498642150302543996}, { 41.899315676124750496, 12.498172763721441925}, { 41.899207869324136766, 12.498057428732948893 }},
|
||||
{{ 41.900315874986389986, 12.499267105007675127}, { 41.900234022973513959, 12.499175909900486658}, { 41.899802800880578957, 12.498703841110341273 }},
|
||||
{{ 41.899317672545265623, 12.499556783583443575}, { 41.899704976945002954, 12.498910371206022774}, { 41.899716955394147533, 12.49888623132471821}, { 41.899730930248637151, 12.498862091443413647}, { 41.899744905100071435, 12.49882990493497914}, { 41.899760876355117034, 12.498797718426573056}, { 41.899772854793766896, 12.498768214127181864}, { 41.899788826041820755, 12.498736027618775779}, { 41.899802800880578957, 12.498703841110341273 }},
|
||||
{{ 41.900297907480371862, 12.497869674100513748}, { 41.900259976062137923, 12.497931364908311025}, { 41.899866685818835776, 12.498593870539906447}, { 41.899834743357700972, 12.498647514720602203}, { 41.899818772121129484, 12.498674336810950081}, { 41.899802800880578957, 12.498703841110341273 }},
|
||||
{{ 41.899728933841061007, 12.497309092412223208}, { 41.900224041013551357, 12.497850898637267392}, { 41.90023202658165502, 12.49786430968242712}, { 41.900224041013551357, 12.497888449563731683}, { 41.899826757739916161, 12.498548272986312213}, { 41.899808790096251698, 12.498556319613442156}, { 41.899802800880578957, 12.498550955195355527}, { 41.89979281885320006, 12.498545590777297321}, { 41.899307690442057606, 12.498009148970311344}, { 41.899301701179375357, 12.497995737925151616}, { 41.899309686862821422, 12.497976962461905259}, { 41.899716955394147533, 12.497311774621238101}, { 41.899728933841061007, 12.497309092412223208 }},
|
||||
{{ 41.899802800880578957, 12.498550955195355527}, { 41.899748897914214751, 12.498642150302543996}, { 41.899681020039992063, 12.498754803082022136 }},
|
||||
{{ 41.899912603078725226, 12.498650196929645517}, { 41.899866685818835776, 12.498593870539906447}, { 41.899826757739916161, 12.498548272986312213 }},
|
||||
{{ 41.899994455503602353, 12.498516086477906128}, { 41.899912603078725226, 12.498650196929645517}, { 41.899912603078725226, 12.498685065647094916}, { 41.900285929140210328, 12.499090079211356397}, { 41.90030589303923847, 12.499111536883646068}, { 41.90070516970908443, 12.498435620206862495}, { 41.900711158840110215, 12.49842489137071766}, { 41.900715151593857399, 12.498408798116514618}, { 41.900713155217019334, 12.498398069280369782}, { 41.90056342677709722, 12.498237136738282516}, { 41.900327853320931126, 12.497979644670920152}, { 41.900317871375655443, 12.497976962461905259}, { 41.900307889428788144, 12.497982326879963466}, { 41.899994455503602353, 12.498516086477906128 }},
|
||||
|
||||
{{ 44.759886801735603967, 34.316046940654871378 }, { 44.759500178870737841, 34.315553414192436321 }, { 44.759132599068138347, 34.315443443622029918 }, { 44.758765016927078761, 34.315430032576841768 }, { 44.758071746835689453, 34.315253006780551459 }, { 44.758037464032938146, 34.315255688989566352 }, { 44.757483222565575431, 34.315306650961247215 }, { 44.756708037437867631, 34.315676795808059296 }, { 44.756323297960172169, 34.315652655926726311 }, { 44.755963316624225001, 34.315430032576841768 }, { 44.755833798981250027, 34.315153765046261469 }, { 44.755789991477485046, 34.314949917159594861 }},
|
||||
{{ 44.759886801735603967, 34.316046940654871378 }, { 44.760006787615907342, 34.315175222718522718 }, { 44.760048687388419353, 34.315011607967392138 }, { 44.760260090322724125, 34.314772891363304552 }, { 44.760437211104594724, 34.314665603001913041 }, { 44.760572431981174191, 34.314383971053246114 }, { 44.760701939002856875, 34.314300822573159166 }, { 44.761223773178279828, 34.314088928059419459 }, { 44.761292334982400121, 34.314091610268434351 }, { 44.761376132632506142, 34.314011143997390718 }, { 44.761667518969709079, 34.313895809008897686 }, { 44.761739889204726239, 34.31379924948365101 }, { 44.761739889204726239, 34.313705372167419227 }, { 44.76183130410882427, 34.313568579506636524 }, { 44.761930336758403826, 34.313549804043390168 }, { 44.761981757490261202, 34.313442515681998657 }, { 44.762050318394869919, 34.313396918128404423 }, { 44.762176013175370315, 34.313391553710346216 }, { 44.762316943361625476, 34.313359367201911709 }, { 44.762610229403847484, 34.313332545111563832 }, { 44.762627369451166714, 34.313343273947708667 }, { 44.762663553978839559, 34.313313769648317475 }, { 44.762673076219179791, 34.313278900930868076 }, { 44.762678789562635018, 34.313233303377273842 }, { 44.762692120695184883, 34.31320379907788265 }, { 44.762720687397411723, 34.313182341405621401 }, { 44.762734018520298207, 34.313107239552635974 }, { 44.762747349640086725, 34.313091146298432932 }, { 44.7628063874193316, 34.313101875134577767 }, { 44.76292065391750441, 34.313123332806839016 }, { 44.762977787081830172, 34.313056277580983533 }, { 44.763063486722373341, 34.313107239552635974 }, { 44.763282496337311045, 34.313088464089389618 }},
|
||||
{{ 44.756521381971580809, 34.332137512655094724 }, { 44.756469956380023234, 34.331619846311355104 }, { 44.756412816780184016, 34.331308710063325407 }, { 44.756359486436011252, 34.331040489159818208 }, { 44.75633282124546497, 34.330965387306861203 }, { 44.756294728094793811, 34.330860781154484584 }, { 44.756264253556160781, 34.330694484194339111 }, { 44.756294728094793811, 34.330608653505208849 }, { 44.756323297960172169, 34.330533551652223423 }, { 44.756433767973362592, 34.330359208064976428 }, { 44.756555665673857902, 34.330109762624715586 }, { 44.756641374840555159, 34.329940783455526798 }, { 44.756755653531747896, 34.329755711032106547 }, { 44.757010875126020721, 34.329463350247323206 }, { 44.757180387352946127, 34.329117345281815687 }, { 44.757266095593031707, 34.32893495506743875 }, { 44.757448939413642108, 34.328564810220626669 }, { 44.757591785745901802, 34.328301953735206098 }, { 44.757746059388104243, 34.327797698436654628 }, { 44.757807006886359602, 34.327470468934393466 }, { 44.75787366813893442, 34.327239798957407402 }, { 44.757986039790637278, 34.327089595251464971 }, { 44.758026036427494887, 34.326995717935233188 }, { 44.758113648011466523, 34.326907205037088033 }, { 44.75817078592944398, 34.326783823421465058 }, { 44.758237446762365153, 34.326400267529493249 }, { 44.758264111073970071, 34.3263519877668557 }, { 44.758357436067782942, 34.326177644179608706 }, { 44.758412669156371066, 34.325909423276101506 }, { 44.758505993910240761, 34.325775312824362118 }, { 44.758526944344453113, 34.325598287028071809 }, { 44.758479329710219474, 34.325461494367289106 }, { 44.758391718680712756, 34.325370299260100637 }, { 44.758429810449001707, 34.32524423543546277 }, { 44.75854789477109108, 34.325085985102390396 }, { 44.758637410144764601, 34.324914323724158294 }, { 44.758685024648755757, 34.324734615718824671 }, { 44.758707879596705936, 34.324613916312273432 }, { 44.758766921503791991, 34.324568318758679197 }, { 44.758749780311177346, 34.32444225493404133 }, { 44.758799299298182461, 34.324206220538968637 }, { 44.75882786792522694, 34.323988961607142301 }, { 44.758825963350531651, 34.323667096522939346 }, { 44.758843104520543932, 34.323423015500765132 }, { 44.758862150259034252, 34.323181616687634232 }, { 44.758915478293424428, 34.322983133219054253 }, { 44.758944046863021526, 34.322875844857662742 }, { 44.759084984933011242, 34.322862433812474592 }, { 44.758966901708539865, 34.32266126813487972 }, { 44.758865959405987667, 34.32256202640058973 }, { 44.758753589465527511, 34.322331356423575244 }, { 44.75867359717138072, 34.322089957610444344 }, { 44.758690738386590624, 34.321875380887661322 }, { 44.758690738386590624, 34.321754681481081661 }, { 44.758698356702829813, 34.321642028701631943 }, { 44.758643123887310367, 34.321379172216211373 }, { 44.758641219306547043, 34.321231650719283834 }, { 44.758665978851873035, 34.321027802832645648 }, { 44.758732639113461005, 34.320837365991167189 }, { 44.75867359717138072, 34.320502089861804507 }, { 44.75867931091034535, 34.320392119291369681 }, { 44.758808822175438991, 34.320268737675775128 }, { 44.758793585571083895, 34.320099758506557919 }, { 44.758978329127899087, 34.319783257840441593 }, { 44.759109744288181787, 34.319584774371861613 }, { 44.75926782299799811, 34.31950430810081798 }, { 44.759357337256332698, 34.319305824632238 }, { 44.759387810163708821, 34.319016146056469552 }, { 44.759481133342561066, 34.318686234345193498 }, { 44.75951351073673834, 34.318530666221164438 }, { 44.759524938047988485, 34.318332182752584458 }, { 44.759637306488322395, 34.317903029306989993 }, { 44.759671588341880977, 34.317436324934931235 }, { 44.759871565415501493, 34.317044722415829483 }, { 44.759902038051663453, 34.316647755478669524 }, { 44.759915369824923914, 34.316167640061422617 }, { 44.759886801735603967, 34.316046940654871378 }},
|
||||
};
|
||||
|
||||
using namespace std;
|
||||
for (auto const & points : arr)
|
||||
{
|
||||
for (auto const & p : points)
|
||||
cout << manager.GetTriangleHeight(p) << ", ";
|
||||
cout << endl;
|
||||
}
|
||||
}
|
||||
*/
|
||||
} // namespace srtm_parser_test
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "routing/maxspeeds_serialization.hpp"
|
||||
#include "routing/routing_helpers.hpp"
|
||||
|
||||
#include "routing_common/car_model_coefs.hpp"
|
||||
#include "routing_common/maxspeed_conversion.hpp"
|
||||
|
||||
#include "indexer/feature.hpp"
|
||||
@@ -37,6 +38,10 @@ using namespace routing;
|
||||
using std::string;
|
||||
|
||||
char constexpr kDelim[] = ", \t\r\n";
|
||||
double constexpr kMinDefSpeedRoadsLengthKm = 5.0;
|
||||
double constexpr kMaxPossibleDefSpeedKmH = 400.0;
|
||||
// This factor should be greater than sqrt(2) / 2 - prefer diagonal link to square path.
|
||||
double constexpr kLinkToMainSpeedFactor = 0.85;
|
||||
|
||||
template <class TokenizerT> bool ParseOneSpeedValue(TokenizerT & iter, MaxspeedType & value)
|
||||
{
|
||||
@@ -68,11 +73,21 @@ class MaxspeedsMwmCollector
|
||||
{
|
||||
double m_lengthKM = 0;
|
||||
double m_timeH = 0;
|
||||
|
||||
double m_speed = -1; // invalid initial value
|
||||
|
||||
friend std::string DebugPrint(AvgInfo const & i)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << "AvgInfo{ " << i.m_speed << ", " << i.m_lengthKM << ", " << i.m_timeH << " }";
|
||||
return ss.str();
|
||||
}
|
||||
};
|
||||
|
||||
static int constexpr SPEEDS_COUNT = MaxspeedsSerializer::DEFAULT_SPEEDS_COUNT;
|
||||
static int constexpr kSpeedsCount = MaxspeedsSerializer::DEFAULT_SPEEDS_COUNT;
|
||||
static int constexpr kOutsideCityIdx = 0;
|
||||
// 0 - outside a city; 1 - inside a city.
|
||||
std::unordered_map<HighwayType, AvgInfo> m_avgSpeeds[SPEEDS_COUNT];
|
||||
std::unordered_map<HighwayType, AvgInfo> m_avgSpeeds[kSpeedsCount];
|
||||
|
||||
base::GeoObjectId GetOsmID(uint32_t fid) const
|
||||
{
|
||||
@@ -87,10 +102,14 @@ class MaxspeedsMwmCollector
|
||||
return m_graph->GetRoadGeometry(fid);
|
||||
}
|
||||
|
||||
// OSM data related warning tag for convenient grep.
|
||||
std::string m_logTag;
|
||||
|
||||
public:
|
||||
MaxspeedsMwmCollector(string const & dataPath, FeatureIdToOsmId const & ft2osm, IndexGraph * graph)
|
||||
: m_dataPath(dataPath), m_ft2osm(ft2osm), m_graph(graph)
|
||||
, m_converter(MaxspeedConverter::Instance())
|
||||
, m_logTag("SpeedsBuilder")
|
||||
{
|
||||
}
|
||||
|
||||
@@ -146,9 +165,8 @@ public:
|
||||
(*parentHwType == HighwayType::HighwayTertiary && hwType == HighwayType::HighwayTertiaryLink))
|
||||
{
|
||||
// Reduce factor from parent road. See DontUseLinksWhenRidingOnMotorway test.
|
||||
// 0.85, this factor should be greater than sqrt(2) / 2 - prefer diagonal link to square path.
|
||||
return converter.ClosestValidMacro(
|
||||
{ base::asserted_cast<MaxspeedType>(std::lround(s.GetForward() * 0.85)), s.GetUnits() });
|
||||
{ base::asserted_cast<MaxspeedType>(std::lround(s.GetForward() * kLinkToMainSpeedFactor)), s.GetUnits() });
|
||||
}
|
||||
|
||||
return {};
|
||||
@@ -265,8 +283,16 @@ public:
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
void AddSpeed(uint32_t featureID, uint64_t osmID, Maxspeed const & speed)
|
||||
{
|
||||
MaxspeedType constexpr kMaxReasonableSpeed = 280;
|
||||
if ((speed.GetSpeedKmPH(true) >= kMaxReasonableSpeed) ||
|
||||
(speed.IsBidirectional() && speed.GetSpeedKmPH(false) >= kMaxReasonableSpeed))
|
||||
{
|
||||
LOG(LWARNING, (m_logTag, "Very big speed", speed, "for way", osmID));
|
||||
}
|
||||
|
||||
// Add converted macro speed.
|
||||
SpeedInUnits const forward(speed.GetForward(), speed.GetUnits());
|
||||
CHECK(forward.IsValid(), ());
|
||||
@@ -278,12 +304,12 @@ public:
|
||||
|
||||
if (ftSpeed.m_forward == SpeedMacro::Undefined)
|
||||
{
|
||||
LOG(LWARNING, ("Undefined forward speed macro", forward, "for way", osmID));
|
||||
LOG(LWARNING, (m_logTag, "Undefined forward speed macro", forward, "for way", osmID));
|
||||
return;
|
||||
}
|
||||
if (backward.IsValid() && backwardMacro == SpeedMacro::Undefined)
|
||||
{
|
||||
LOG(LWARNING, ("Undefined backward speed macro", backward, "for way", osmID));
|
||||
LOG(LWARNING, (m_logTag, "Undefined backward speed macro", backward, "for way", osmID));
|
||||
}
|
||||
|
||||
m_maxspeeds.push_back(ftSpeed);
|
||||
@@ -311,36 +337,167 @@ public:
|
||||
}
|
||||
}
|
||||
else
|
||||
LOG(LWARNING, ("Undefined HighwayType for way", osmID));
|
||||
LOG(LWARNING, (m_logTag, "Undefined HighwayType for way", osmID));
|
||||
}
|
||||
|
||||
void SerializeMaxspeeds() const
|
||||
public:
|
||||
void CalculateDefaultTypeSpeeds(MaxspeedsSerializer::HW2SpeedMap typeSpeeds[])
|
||||
{
|
||||
if (m_maxspeeds.empty())
|
||||
return;
|
||||
|
||||
MaxspeedsSerializer::HW2SpeedMap typeSpeeds[SPEEDS_COUNT];
|
||||
for (int ind = 0; ind < SPEEDS_COUNT; ++ind)
|
||||
std::vector<std::pair<HighwayType, InOutCitySpeedKMpH>> baseSpeeds(
|
||||
kHighwayBasedSpeeds.begin(), kHighwayBasedSpeeds.end());
|
||||
// Remove links, because they don't conform speed consistency.
|
||||
baseSpeeds.erase(std::remove_if(baseSpeeds.begin(), baseSpeeds.end(), [](auto const & e)
|
||||
{
|
||||
LOG(LINFO, ("Average speeds", ind == 0 ? "outside" : "inside", "a city:"));
|
||||
return (e.first == HighwayType::HighwayMotorwayLink ||
|
||||
e.first == HighwayType::HighwayTrunkLink ||
|
||||
e.first == HighwayType::HighwayPrimaryLink ||
|
||||
e.first == HighwayType::HighwaySecondaryLink ||
|
||||
e.first == HighwayType::HighwayTertiaryLink);
|
||||
}), baseSpeeds.end());
|
||||
|
||||
for (int ind = 0; ind < kSpeedsCount; ++ind)
|
||||
{
|
||||
// Calculate average speed.
|
||||
for (auto & e : m_avgSpeeds[ind])
|
||||
{
|
||||
// Check some reasonable conditions when assigning average speed.
|
||||
if (e.second.m_lengthKM > kMinDefSpeedRoadsLengthKm)
|
||||
{
|
||||
auto const speed = e.second.m_lengthKM / e.second.m_timeH;
|
||||
if (speed < kMaxPossibleDefSpeedKmH)
|
||||
e.second.m_speed = speed;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare ethalon vector.
|
||||
bool const inCity = ind != kOutsideCityIdx;
|
||||
std::sort(baseSpeeds.begin(), baseSpeeds.end(), [inCity](auto const & l, auto const & r)
|
||||
{
|
||||
// Sort from biggest to smallest.
|
||||
return r.second.GetSpeed(inCity).m_weight < l.second.GetSpeed(inCity).m_weight;
|
||||
});
|
||||
|
||||
// First of all check that calculated speed and base speed difference is less than 2x.
|
||||
for (auto const & e : baseSpeeds)
|
||||
{
|
||||
auto & l = m_avgSpeeds[ind][e.first];
|
||||
if (l.m_speed > 0)
|
||||
{
|
||||
double const base = e.second.GetSpeed(inCity).m_weight;
|
||||
double const factor = l.m_speed / base;
|
||||
if (factor > 2 || factor < 0.5)
|
||||
{
|
||||
LOG(LWARNING, (m_logTag, "More than 2x diff:", e.first, l.m_speed, base));
|
||||
l.m_speed = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check speed's pairs consistency.
|
||||
// Constraints from the previous iteration can be broken if we modify l-speed on the next iteration.
|
||||
for (size_t il = 0, ir = 1; ir < baseSpeeds.size(); ++ir)
|
||||
{
|
||||
auto & l = m_avgSpeeds[ind][baseSpeeds[il].first];
|
||||
if (l.m_speed < 0)
|
||||
{
|
||||
++il;
|
||||
continue;
|
||||
}
|
||||
auto & r = m_avgSpeeds[ind][baseSpeeds[ir].first];
|
||||
if (r.m_speed < 0)
|
||||
continue;
|
||||
|
||||
// |l| should be greater than |r|
|
||||
if (l.m_speed < r.m_speed)
|
||||
{
|
||||
LOG(LWARNING, (m_logTag, "Bad def speeds pair:", baseSpeeds[il].first, baseSpeeds[ir].first, l, r));
|
||||
|
||||
if (l.m_lengthKM >= r.m_lengthKM)
|
||||
r.m_speed = l.m_speed;
|
||||
else
|
||||
l.m_speed = r.m_speed;
|
||||
}
|
||||
|
||||
il = ir;
|
||||
}
|
||||
|
||||
auto const getSpeed = [this, ind, inCity](HighwayType type)
|
||||
{
|
||||
auto const s = m_avgSpeeds[ind][type].m_speed;
|
||||
if (s > 0)
|
||||
return s;
|
||||
auto const * p = kHighwayBasedSpeeds.Find(type);
|
||||
CHECK(p, ());
|
||||
return p->GetSpeed(inCity).m_weight;
|
||||
};
|
||||
|
||||
// These speeds: Primary, Secondary, Tertiary, Residential have the biggest routing quality impact.
|
||||
{
|
||||
double const primaryS = getSpeed(HighwayType::HighwayPrimary);
|
||||
double const secondaryS = getSpeed(HighwayType::HighwaySecondary);
|
||||
double const tertiaryS = getSpeed(HighwayType::HighwayTertiary);
|
||||
double const residentialS = getSpeed(HighwayType::HighwayResidential);
|
||||
double constexpr eps = 1.0;
|
||||
if (primaryS + eps < secondaryS || secondaryS + eps < tertiaryS || tertiaryS + eps < residentialS)
|
||||
{
|
||||
LOG(LWARNING, (m_logTag, "Ignore primary, secondary, tertiary, residential speeds:",
|
||||
primaryS, secondaryS, tertiaryS, residentialS));
|
||||
|
||||
m_avgSpeeds[ind][HighwayType::HighwayPrimary].m_speed = -1;
|
||||
m_avgSpeeds[ind][HighwayType::HighwaySecondary].m_speed = -1;
|
||||
m_avgSpeeds[ind][HighwayType::HighwayTertiary].m_speed = -1;
|
||||
m_avgSpeeds[ind][HighwayType::HighwayResidential].m_speed = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Update links.
|
||||
std::pair<HighwayType, HighwayType> arrLinks[] = {
|
||||
{HighwayType::HighwayMotorway, HighwayType::HighwayMotorwayLink},
|
||||
{HighwayType::HighwayTrunk, HighwayType::HighwayTrunkLink},
|
||||
{HighwayType::HighwayPrimary, HighwayType::HighwayPrimaryLink},
|
||||
{HighwayType::HighwaySecondary, HighwayType::HighwaySecondaryLink},
|
||||
{HighwayType::HighwayTertiary, HighwayType::HighwayTertiaryLink},
|
||||
};
|
||||
for (auto const & e : arrLinks)
|
||||
{
|
||||
auto const main = m_avgSpeeds[ind][e.first].m_speed;
|
||||
auto & link = m_avgSpeeds[ind][e.second].m_speed;
|
||||
if (main > 0)
|
||||
link = kLinkToMainSpeedFactor * main;
|
||||
else
|
||||
link = -1;
|
||||
}
|
||||
|
||||
// Fill type-speed map.
|
||||
LOG(LINFO, ("Average speeds", ind == kOutsideCityIdx ? "outside" : "inside", "a city:"));
|
||||
for (auto const & e : m_avgSpeeds[ind])
|
||||
{
|
||||
long const speed = std::lround(e.second.m_lengthKM / e.second.m_timeH);
|
||||
if (speed < routing::kInvalidSpeed)
|
||||
if (e.second.m_speed > 0)
|
||||
{
|
||||
// Store type speeds in Metric system, like VehicleModel profiles.
|
||||
auto const speedInUnits = m_converter.ClosestValidMacro(
|
||||
{ static_cast<MaxspeedType>(speed), measurement_utils::Units::Metric });
|
||||
{ static_cast<MaxspeedType>(e.second.m_speed), measurement_utils::Units::Metric });
|
||||
|
||||
LOG(LINFO, ("*", e.first, "=", speedInUnits));
|
||||
|
||||
typeSpeeds[ind][e.first] = m_converter.SpeedToMacro(speedInUnits);
|
||||
}
|
||||
else
|
||||
LOG(LWARNING, ("Large average speed for", e.first, "=", speed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SerializeMaxspeeds()
|
||||
{
|
||||
if (m_maxspeeds.empty())
|
||||
return;
|
||||
|
||||
MaxspeedsSerializer::HW2SpeedMap typeSpeeds[kSpeedsCount];
|
||||
/// @todo There are too many claims/bugs with Turkey calculated defaults.
|
||||
/// And yes, now this dummy country check :)
|
||||
if (m_dataPath.find("Turkey_") == std::string::npos)
|
||||
CalculateDefaultTypeSpeeds(typeSpeeds);
|
||||
|
||||
// Serialize speeds.
|
||||
FilesContainerW cont(m_dataPath, FileWriter::OP_WRITE_EXISTING);
|
||||
auto writer = cont.GetWriter(MAXSPEEDS_FILE_TAG);
|
||||
MaxspeedsSerializer::Serialize(m_maxspeeds, typeSpeeds, *writer);
|
||||
|
||||
@@ -4,14 +4,10 @@
|
||||
|
||||
#include "routing_common/maxspeed_conversion.hpp"
|
||||
|
||||
#include "platform/measurement_utils.hpp"
|
||||
|
||||
#include "base/geo_object_id.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace routing
|
||||
{
|
||||
|
||||
@@ -209,7 +209,11 @@ void RelationTagsWay::Process(RelationElement const & e)
|
||||
continue;
|
||||
|
||||
// Do not pass "ref" tags from boundaries and other, non-route relations to highways.
|
||||
if (p.first == "ref" && isHighway)
|
||||
if (isHighway && p.first == "ref")
|
||||
continue;
|
||||
|
||||
// https://github.com/organicmaps/organicmaps/issues/8246
|
||||
if (type == "route" && (p.first == "oneway" || p.first == "surface"))
|
||||
continue;
|
||||
|
||||
Base::AddCustomTag(p);
|
||||
|
||||
@@ -2,72 +2,116 @@
|
||||
|
||||
#include "routing/routing_helpers.hpp"
|
||||
|
||||
#include "indexer/classificator.hpp"
|
||||
#include "indexer/classificator_loader.hpp"
|
||||
#include "indexer/feature.hpp"
|
||||
#include "indexer/feature_altitude.hpp"
|
||||
#include "indexer/feature_data.hpp"
|
||||
#include "indexer/feature_processor.hpp"
|
||||
|
||||
#include "geometry/distance_on_sphere.hpp"
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include "platform/country_file.hpp"
|
||||
#include "platform/local_country_file.hpp"
|
||||
#include "platform/local_country_file_utils.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
DEFINE_string(srtm_path, "", "Path to directory with SRTM files");
|
||||
DEFINE_string(mwm_path, "", "Path to mwm files (writable dir)");
|
||||
DEFINE_bool(check_dist, false, "Check feature sections distance");
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
class SafeTileManager
|
||||
{
|
||||
gflags::SetUsageMessage("SRTM coverage checker.");
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
generator::SrtmTileManager m_manager;
|
||||
std::mutex m_mutex;
|
||||
|
||||
Platform & platform = GetPlatform();
|
||||
if (!FLAGS_mwm_path.empty())
|
||||
platform.SetWritableDirForTests(FLAGS_mwm_path);
|
||||
uint32_t m_ferry;
|
||||
|
||||
if (FLAGS_srtm_path.empty())
|
||||
public:
|
||||
explicit SafeTileManager(std::string const & dir) : m_manager(dir)
|
||||
{
|
||||
LOG(LERROR, ("SRTM files directory is not specified."));
|
||||
return -1;
|
||||
m_ferry = classif().GetTypeByPath({"route", "ferry"});
|
||||
CHECK(m_ferry != Classificator::INVALID_TYPE, ());
|
||||
}
|
||||
|
||||
LOG(LINFO, ("writable dir =", platform.WritableDir()));
|
||||
LOG(LINFO, ("srtm dir =", FLAGS_srtm_path));
|
||||
bool IsAltitudeRoad(FeatureType & ft) const
|
||||
{
|
||||
feature::TypesHolder types(ft);
|
||||
return (routing::IsRoad(types) && !types.Has(m_ferry));
|
||||
}
|
||||
|
||||
geometry::Altitude GetAltitude(ms::LatLon const & coord)
|
||||
{
|
||||
std::lock_guard guard(m_mutex);
|
||||
return m_manager.GetAltitude(coord);
|
||||
}
|
||||
|
||||
void Purge()
|
||||
{
|
||||
std::lock_guard guard(m_mutex);
|
||||
m_manager.Purge();
|
||||
}
|
||||
};
|
||||
|
||||
template <class FnT> void ForEachMWM(SafeTileManager & manager, FnT && fn)
|
||||
{
|
||||
std::vector<platform::LocalCountryFile> localFiles;
|
||||
FindAllLocalMapsAndCleanup(std::numeric_limits<int64_t>::max() /* latestVersion */, localFiles);
|
||||
|
||||
generator::SrtmTileManager manager(FLAGS_srtm_path);
|
||||
classificator::Load();
|
||||
// Better use ComputationalThreadPool, but we want to call SafeTileManager::Purge after each batch.
|
||||
size_t constexpr kThreadsCount = 24;
|
||||
std::vector<std::thread> pool;
|
||||
|
||||
size_t workers = 0;
|
||||
for (auto & file : localFiles)
|
||||
{
|
||||
// Skip worlds.
|
||||
if (file.GetDirectory().empty() || file.GetCountryName().starts_with("World"))
|
||||
continue;
|
||||
|
||||
file.SyncWithDisk();
|
||||
if (!file.OnDisk(MapFileType::Map))
|
||||
{
|
||||
LOG(LINFO, ("Warning! Routing file not found for:", file.GetCountryName()));
|
||||
LOG_SHORT(LWARNING, ("Map file not found for:", file.GetCountryName()));
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const path = file.GetPath(MapFileType::Map);
|
||||
LOG(LINFO, ("Mwm", path, "is being processed."));
|
||||
LOG_SHORT(LINFO, ("Processing", file.GetCountryName()));
|
||||
|
||||
pool.emplace_back([&fn, &file]() { fn(file); });
|
||||
|
||||
if (++workers == kThreadsCount)
|
||||
{
|
||||
for (auto & t : pool)
|
||||
t.join();
|
||||
pool.clear();
|
||||
|
||||
manager.Purge();
|
||||
workers = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto & t : pool)
|
||||
t.join();
|
||||
}
|
||||
|
||||
void CheckCoverage(SafeTileManager & manager)
|
||||
{
|
||||
ForEachMWM(manager, [&](platform::LocalCountryFile const & file)
|
||||
{
|
||||
size_t all = 0;
|
||||
size_t good = 0;
|
||||
feature::ForEachFeature(path, [&](FeatureType & ft, uint32_t fid) {
|
||||
if (!routing::IsRoad(feature::TypesHolder(ft)))
|
||||
feature::ForEachFeature(file.GetPath(MapFileType::Map), [&](FeatureType & ft, uint32_t)
|
||||
{
|
||||
if (!manager.IsAltitudeRoad(ft))
|
||||
return;
|
||||
|
||||
ft.ParseGeometry(FeatureType::BEST_GEOMETRY);
|
||||
@@ -75,16 +119,136 @@ int main(int argc, char * argv[])
|
||||
|
||||
for (size_t i = 0; i < ft.GetPointsCount(); ++i)
|
||||
{
|
||||
auto const height = manager.GetHeight(mercator::ToLatLon(ft.GetPoint(i)));
|
||||
auto const height = manager.GetAltitude(mercator::ToLatLon(ft.GetPoint(i)));
|
||||
if (height != geometry::kInvalidAltitude)
|
||||
good++;
|
||||
}
|
||||
});
|
||||
|
||||
auto const bad = all - good;
|
||||
auto const percent = all == 0 ? 0.0 : bad * 100.0 / all;
|
||||
LOG(LINFO, (percent > 10.0 ? "Huge" : "Low", "error rate in:", file.GetCountryName(),
|
||||
"good:", good, "bad:", bad, "all:", all, "%:", percent));
|
||||
auto const percent = (all == 0) ? 0.0 : bad * 100.0 / all;
|
||||
LOG_SHORT(LINFO, (percent > 10.0 ? "Huge" : "Low", "error rate in:", file.GetCountryName(),
|
||||
"good:", good, "bad:", bad, "all:", all, "%:", percent));
|
||||
});
|
||||
}
|
||||
|
||||
void CheckDistance(SafeTileManager & manager)
|
||||
{
|
||||
ForEachMWM(manager, [&](platform::LocalCountryFile const & file)
|
||||
{
|
||||
size_t all = 0;
|
||||
size_t added = 0;
|
||||
size_t invalid = 0;
|
||||
feature::ForEachFeature(file.GetPath(MapFileType::Map), [&](FeatureType & ft, uint32_t)
|
||||
{
|
||||
if (!manager.IsAltitudeRoad(ft))
|
||||
return;
|
||||
|
||||
ft.ParseGeometry(FeatureType::BEST_GEOMETRY);
|
||||
all += ft.GetPointsCount();
|
||||
|
||||
for (size_t i = 1; i < ft.GetPointsCount(); ++i)
|
||||
{
|
||||
auto const ll1 = mercator::ToLatLon(ft.GetPoint(i-1));
|
||||
auto const alt1 = manager.GetAltitude(ll1);
|
||||
auto const ll2 = mercator::ToLatLon(ft.GetPoint(i));
|
||||
auto const alt2 = manager.GetAltitude(ll2);
|
||||
|
||||
if (alt1 == geometry::kInvalidAltitude || alt2 == geometry::kInvalidAltitude)
|
||||
{
|
||||
++invalid;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Divide by 1 second sections.
|
||||
size_t const sections = std::round(ms::DistanceOnSphere(ll1.m_lat, ll1.m_lon, ll2.m_lat, ll2.m_lon) * 3600);
|
||||
if (sections < 2)
|
||||
continue;
|
||||
|
||||
for (size_t j = 1; j < sections; ++j)
|
||||
{
|
||||
double const a = j / double(sections);
|
||||
ms::LatLon const ll(ll2.m_lat * a + ll1.m_lat * (1 - a), ll2.m_lon * a + ll1.m_lon * (1 - a));
|
||||
|
||||
// Get diff between approx altitude and real one.
|
||||
auto const alt = manager.GetAltitude(ll);
|
||||
if (alt == geometry::kInvalidAltitude)
|
||||
{
|
||||
LOG_SHORT(LWARNING, ("Invalid altitude for the middle point:", ll));
|
||||
++added;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const approxAlt = static_cast<geometry::Altitude>(std::round(alt2 * a + alt1 * (1 - a)));
|
||||
if (abs(alt - approxAlt) >= std::max(1, abs(alt)/10)) // 10%
|
||||
++added;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
auto const percent = added * 100.0 / all;
|
||||
std::string prefix = "Low";
|
||||
if (percent >= 1)
|
||||
prefix = "Huge";
|
||||
else if (added >= 1000)
|
||||
prefix = "Medium";
|
||||
|
||||
LOG_SHORT(LINFO, (prefix, file.GetCountryName(), "all:", all, "invalid:", invalid, "added:", added, "%:", percent));
|
||||
});
|
||||
}
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
gflags::SetUsageMessage("SRTM coverage checker.");
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
if (FLAGS_srtm_path.empty())
|
||||
{
|
||||
LOG_SHORT(LERROR, ("SRTM files directory is not specified."));
|
||||
return -1;
|
||||
}
|
||||
|
||||
classificator::Load();
|
||||
|
||||
if (!FLAGS_mwm_path.empty())
|
||||
{
|
||||
SafeTileManager manager(FLAGS_srtm_path);
|
||||
|
||||
Platform & platform = GetPlatform();
|
||||
platform.SetWritableDirForTests(FLAGS_mwm_path);
|
||||
|
||||
if (FLAGS_check_dist)
|
||||
CheckDistance(manager);
|
||||
else
|
||||
CheckCoverage(manager);
|
||||
}
|
||||
else
|
||||
{
|
||||
generator::SrtmTileManager manager(FLAGS_srtm_path);
|
||||
|
||||
using namespace std;
|
||||
cout << "Enter lat lon. Or Ctrl + C to exit." << endl;
|
||||
|
||||
while (true)
|
||||
{
|
||||
double lat, lon;
|
||||
cin >> lat >> lon;
|
||||
if (!cin)
|
||||
{
|
||||
cout << "Invalid lat lon." << endl;
|
||||
cin.clear();
|
||||
cin.ignore(10000, '\n');
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const & tile = manager.GetTile({lat, lon});
|
||||
cout << "H = " << tile.GetHeight({lat, lon}) <<
|
||||
"; Trg = " << tile.GetTriangleHeight({lat, lon}) <<
|
||||
"; Bilinear = " << tile.GetBilinearHeight({lat, lon});
|
||||
cout << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -16,15 +16,21 @@ namespace generator
|
||||
{
|
||||
namespace
|
||||
{
|
||||
size_t constexpr kArcSecondsInDegree = 60 * 60;
|
||||
int constexpr kArcSecondsInDegree = 60 * 60;
|
||||
size_t constexpr kSrtmTileSize = (kArcSecondsInDegree + 1) * (kArcSecondsInDegree + 1) * 2;
|
||||
|
||||
struct UnzipMemDelegate : public ZipFileReader::Delegate
|
||||
{
|
||||
explicit UnzipMemDelegate(std::string & buffer) : m_buffer(buffer), m_completed(false) {}
|
||||
explicit UnzipMemDelegate(std::vector<uint8_t> & buffer) : m_buffer(buffer), m_completed(false)
|
||||
{
|
||||
m_buffer.reserve(kSrtmTileSize);
|
||||
}
|
||||
|
||||
// ZipFileReader::Delegate overrides:
|
||||
void OnBlockUnzipped(size_t size, char const * data) override { m_buffer.append(data, size); }
|
||||
void OnBlockUnzipped(size_t size, char const * data) override
|
||||
{
|
||||
m_buffer.insert(m_buffer.end(), data, data + size);
|
||||
}
|
||||
|
||||
void OnStarted() override
|
||||
{
|
||||
@@ -34,7 +40,7 @@ struct UnzipMemDelegate : public ZipFileReader::Delegate
|
||||
|
||||
void OnCompleted() override { m_completed = true; }
|
||||
|
||||
std::string & m_buffer;
|
||||
std::vector<uint8_t> & m_buffer;
|
||||
bool m_completed;
|
||||
};
|
||||
|
||||
@@ -45,11 +51,6 @@ std::string GetSrtmContFileName(std::string const & dir, std::string const & bas
|
||||
} // namespace
|
||||
|
||||
// SrtmTile ----------------------------------------------------------------------------------------
|
||||
SrtmTile::SrtmTile()
|
||||
{
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
SrtmTile::SrtmTile(SrtmTile && rhs) : m_data(std::move(rhs.m_data)), m_valid(rhs.m_valid)
|
||||
{
|
||||
rhs.Invalidate();
|
||||
@@ -90,7 +91,7 @@ void SrtmTile::Init(std::string const & dir, ms::LatLon const & coord)
|
||||
}
|
||||
else
|
||||
{
|
||||
GetPlatform().GetReader(file)->ReadAsString(m_data);
|
||||
m_data = base::ReadFile(base::JoinPath(dir, file));
|
||||
}
|
||||
|
||||
if (m_data.size() != kSrtmTileSize)
|
||||
@@ -103,11 +104,9 @@ void SrtmTile::Init(std::string const & dir, ms::LatLon const & coord)
|
||||
m_valid = true;
|
||||
}
|
||||
|
||||
geometry::Altitude SrtmTile::GetHeight(ms::LatLon const & coord) const
|
||||
// static
|
||||
ms::LatLon SrtmTile::GetCoordInSeconds(ms::LatLon const & coord)
|
||||
{
|
||||
if (!IsValid())
|
||||
return geometry::kInvalidAltitude;
|
||||
|
||||
double ln = coord.m_lon - static_cast<int>(coord.m_lon);
|
||||
if (ln < 0)
|
||||
ln += 1;
|
||||
@@ -116,15 +115,98 @@ geometry::Altitude SrtmTile::GetHeight(ms::LatLon const & coord) const
|
||||
lt += 1;
|
||||
lt = 1 - lt; // from North to South
|
||||
|
||||
auto const row = static_cast<size_t>(std::round(kArcSecondsInDegree * lt));
|
||||
auto const col = static_cast<size_t>(std::round(kArcSecondsInDegree * ln));
|
||||
return { kArcSecondsInDegree * lt, kArcSecondsInDegree * ln };
|
||||
}
|
||||
|
||||
geometry::Altitude SrtmTile::GetHeight(ms::LatLon const & coord) const
|
||||
{
|
||||
if (!IsValid())
|
||||
return geometry::kInvalidAltitude;
|
||||
|
||||
auto const ll = GetCoordInSeconds(coord);
|
||||
|
||||
return GetHeightRC(static_cast<size_t>(std::round(ll.m_lat)), static_cast<size_t>(std::round(ll.m_lon)));
|
||||
}
|
||||
|
||||
geometry::Altitude SrtmTile::GetHeightRC(size_t row, size_t col) const
|
||||
{
|
||||
size_t const ix = row * (kArcSecondsInDegree + 1) + col;
|
||||
|
||||
CHECK_LESS(ix, Size(), (coord));
|
||||
CHECK_LESS(ix, Size(), (row, col));
|
||||
return ReverseByteOrder(Data()[ix]);
|
||||
}
|
||||
|
||||
double SrtmTile::GetTriangleHeight(ms::LatLon const & coord) const
|
||||
{
|
||||
if (!IsValid())
|
||||
return geometry::kInvalidAltitude;
|
||||
|
||||
auto const ll = GetCoordInSeconds(coord);
|
||||
|
||||
m2::Point<int> const p1(static_cast<int>(std::round(ll.m_lon)), static_cast<int>(std::round(ll.m_lat)));
|
||||
|
||||
auto p2 = p1;
|
||||
if (p2.x > ll.m_lon)
|
||||
{
|
||||
if (p2.x > 0)
|
||||
--p2.x;
|
||||
}
|
||||
else if (p2.x < ll.m_lon)
|
||||
{
|
||||
if (p2.x < kArcSecondsInDegree)
|
||||
++p2.x;
|
||||
}
|
||||
|
||||
auto p3 = p1;
|
||||
if (p3.y > ll.m_lat)
|
||||
{
|
||||
if (p3.y > 0)
|
||||
--p3.y;
|
||||
}
|
||||
else if (p3.y < ll.m_lat)
|
||||
{
|
||||
if (p3.y < kArcSecondsInDegree)
|
||||
++p3.y;
|
||||
}
|
||||
|
||||
// Approximate height from triangle p1, p2, p3.
|
||||
// p1.y == p2.y; p1.x == p3.x
|
||||
// https://stackoverflow.com/questions/36090269/finding-height-of-point-on-height-map-triangles
|
||||
int const det = (p2.y - p3.y) * (p1.x - p3.x) + (p3.x - p2.x) * (p1.y - p3.y);
|
||||
if (det == 0)
|
||||
return GetHeightRC(p1.y, p1.x);
|
||||
|
||||
double const a1 = ((p2.y - p3.y) * (ll.m_lon - p3.x) + (p3.x - p2.x) * (ll.m_lat - p3.y)) / det;
|
||||
double const a2 = ((p3.y - p1.y) * (ll.m_lon - p3.x) + (p1.x - p3.x) * (ll.m_lat - p3.y)) / det;
|
||||
double const a3 = 1 - a1 - a2;
|
||||
|
||||
return a1 * GetHeightRC(p1.y, p1.x) + a2 * GetHeightRC(p2.y, p2.x) + a3 * GetHeightRC(p3.y, p3.x);
|
||||
}
|
||||
|
||||
double SrtmTile::GetBilinearHeight(ms::LatLon const & coord) const
|
||||
{
|
||||
if (!IsValid())
|
||||
return geometry::kInvalidAltitude;
|
||||
|
||||
auto const ll = GetCoordInSeconds(coord);
|
||||
|
||||
m2::Point<int> const p1(static_cast<int>(ll.m_lon), static_cast<int>(ll.m_lat));
|
||||
auto p2 = p1;
|
||||
if (p2.x < kArcSecondsInDegree)
|
||||
++p2.x;
|
||||
if (p2.y < kArcSecondsInDegree)
|
||||
++p2.y;
|
||||
|
||||
// https://en.wikipedia.org/wiki/Bilinear_interpolation
|
||||
double const denom = (p2.x - p1.x) * (p2.y - p1.y);
|
||||
if (denom == 0)
|
||||
return GetHeightRC(p1.y, p1.x);
|
||||
|
||||
return (GetHeightRC(p1.y, p1.x) * (p2.x - ll.m_lon) * (p2.y - ll.m_lat) +
|
||||
GetHeightRC(p1.y, p2.x) * (ll.m_lon - p1.x) * (p2.y - ll.m_lat) +
|
||||
GetHeightRC(p2.y, p1.x) * (p2.x - ll.m_lon) * (ll.m_lat - p1.y) +
|
||||
GetHeightRC(p2.y, p2.x) * (ll.m_lon - p1.x) * (ll.m_lat - p1.y)) / denom;
|
||||
}
|
||||
|
||||
// static
|
||||
std::string SrtmTile::GetPath(std::string const & dir, std::string const & base)
|
||||
{
|
||||
@@ -132,42 +214,52 @@ std::string SrtmTile::GetPath(std::string const & dir, std::string const & base)
|
||||
}
|
||||
|
||||
// static
|
||||
ms::LatLon SrtmTile::GetCenter(ms::LatLon const & coord)
|
||||
SrtmTile::LatLonKey SrtmTile::GetKey(ms::LatLon const & coord)
|
||||
{
|
||||
return {floor(coord.m_lat) + 0.5, floor(coord.m_lon) + 0.5};
|
||||
ms::LatLon center{floor(coord.m_lat) + 0.5, floor(coord.m_lon) + 0.5};
|
||||
if (coord.m_lat < 0)
|
||||
center.m_lat -= 1.0;
|
||||
if (coord.m_lon < 0)
|
||||
center.m_lon -= 1.0;
|
||||
|
||||
return {static_cast<int32_t>(center.m_lat), static_cast<int32_t>(center.m_lon)};
|
||||
}
|
||||
|
||||
// static
|
||||
std::string SrtmTile::GetBase(ms::LatLon const & coord)
|
||||
{
|
||||
auto center = GetCenter(coord);
|
||||
auto key = GetKey(coord);
|
||||
std::ostringstream ss;
|
||||
if (center.m_lat < 0)
|
||||
if (coord.m_lat < 0)
|
||||
{
|
||||
ss << "S";
|
||||
center.m_lat *= -1;
|
||||
center.m_lat += 1;
|
||||
key.first = -key.first;
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "N";
|
||||
}
|
||||
ss << std::setw(2) << std::setfill('0') << static_cast<int>(center.m_lat);
|
||||
|
||||
if (center.m_lon < 0)
|
||||
ss << std::setw(2) << std::setfill('0') << key.first;
|
||||
|
||||
if (coord.m_lon < 0)
|
||||
{
|
||||
ss << "W";
|
||||
center.m_lon *= -1;
|
||||
center.m_lon += 1;
|
||||
key.second = -key.second;
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "E";
|
||||
}
|
||||
ss << std::setw(3) << static_cast<int>(center.m_lon);
|
||||
|
||||
ss << std::setw(3) << key.second;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
geometry::Altitude * SrtmTile::DataForTests(size_t & sz)
|
||||
{
|
||||
m_valid = true;
|
||||
sz = kArcSecondsInDegree + 1;
|
||||
m_data.resize(kSrtmTileSize, 0);
|
||||
return reinterpret_cast<geometry::Altitude *>(m_data.data());
|
||||
}
|
||||
|
||||
void SrtmTile::Invalidate()
|
||||
{
|
||||
m_data.clear();
|
||||
@@ -176,47 +268,27 @@ void SrtmTile::Invalidate()
|
||||
}
|
||||
|
||||
// SrtmTileManager ---------------------------------------------------------------------------------
|
||||
SrtmTileManager::SrtmTileManager(std::string const & dir) : m_dir(dir) {}
|
||||
geometry::Altitude SrtmTileManager::GetHeight(ms::LatLon const & coord)
|
||||
SrtmTile const & SrtmTileManager::GetTile(ms::LatLon const & coord)
|
||||
{
|
||||
auto const key = GetKey(coord);
|
||||
|
||||
auto it = m_tiles.find(key);
|
||||
if (it == m_tiles.end())
|
||||
auto res = m_tiles.emplace(SrtmTile::GetKey(coord), SrtmTile());
|
||||
if (res.second)
|
||||
{
|
||||
SrtmTile tile;
|
||||
try
|
||||
{
|
||||
tile.Init(m_dir, coord);
|
||||
res.first->second.Init(m_dir, coord);
|
||||
}
|
||||
catch (RootException const & e)
|
||||
{
|
||||
std::string const base = SrtmTile::GetBase(coord);
|
||||
LOG(LINFO, ("Can't init SRTM tile:", base, "reason:", e.Msg()));
|
||||
}
|
||||
|
||||
// It's OK to store even invalid tiles and return invalid height
|
||||
// for them later.
|
||||
it = m_tiles.emplace(key, std::move(tile)).first;
|
||||
}
|
||||
|
||||
return it->second.GetHeight(coord);
|
||||
return res.first->second;
|
||||
}
|
||||
|
||||
// static
|
||||
SrtmTileManager::LatLonKey SrtmTileManager::GetKey(ms::LatLon const & coord)
|
||||
void SrtmTileManager::Purge()
|
||||
{
|
||||
auto const tileCenter = SrtmTile::GetCenter(coord);
|
||||
return {static_cast<int32_t>(tileCenter.m_lat), static_cast<int32_t>(tileCenter.m_lon)};
|
||||
MapT().swap(m_tiles);
|
||||
}
|
||||
|
||||
SrtmTile const & SrtmTileManager::GetTile(ms::LatLon const & coord)
|
||||
{
|
||||
// Touch the tile to force its loading.
|
||||
GetHeight(coord);
|
||||
auto const key = GetKey(coord);
|
||||
auto const it = m_tiles.find(key);
|
||||
CHECK(it != m_tiles.end(), (coord));
|
||||
return it->second;
|
||||
}
|
||||
} // namespace generator
|
||||
|
||||
@@ -2,44 +2,67 @@
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
|
||||
#include "indexer/feature_altitude.hpp"
|
||||
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include "base/macros.hpp"
|
||||
|
||||
#include <boost/container_hash/hash.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace generator
|
||||
{
|
||||
class SrtmTile
|
||||
{
|
||||
public:
|
||||
SrtmTile();
|
||||
SrtmTile() : m_valid(false) {}
|
||||
SrtmTile(SrtmTile && rhs);
|
||||
|
||||
void Init(std::string const & dir, ms::LatLon const & coord);
|
||||
|
||||
inline bool IsValid() const { return m_valid; }
|
||||
// Returns height in meters at |coord| or kInvalidAltitude.
|
||||
/// @return Height in meters at |coord| or kInvalidAltitude.
|
||||
/// @{
|
||||
/// Nearest serialized height.
|
||||
geometry::Altitude GetHeight(ms::LatLon const & coord) const;
|
||||
/// Triangle interpolation.
|
||||
double GetTriangleHeight(ms::LatLon const & coord) const;
|
||||
/// Bilinear interpolation.
|
||||
double GetBilinearHeight(ms::LatLon const & coord) const;
|
||||
|
||||
geometry::Altitude GetAltitude(ms::LatLon const & coord) const
|
||||
{
|
||||
return static_cast<geometry::Altitude>(std::round(GetBilinearHeight(coord)));
|
||||
}
|
||||
/// @}
|
||||
|
||||
using LatLonKey = std::pair<int32_t, int32_t>;
|
||||
static LatLonKey GetKey(ms::LatLon const & coord);
|
||||
|
||||
static std::string GetBase(ms::LatLon const & coord);
|
||||
static ms::LatLon GetCenter(ms::LatLon const & coord);
|
||||
static std::string GetPath(std::string const & dir, std::string const & base);
|
||||
|
||||
/// Used in unit tests only to prepare mock tile.
|
||||
geometry::Altitude * DataForTests(size_t & sz);
|
||||
|
||||
private:
|
||||
static ms::LatLon GetCoordInSeconds(ms::LatLon const & coord);
|
||||
geometry::Altitude GetHeightRC(size_t row, size_t col) const;
|
||||
|
||||
inline geometry::Altitude const * Data() const
|
||||
{
|
||||
return reinterpret_cast<geometry::Altitude const *>(m_data.data());
|
||||
};
|
||||
}
|
||||
|
||||
inline size_t Size() const { return m_data.size() / sizeof(geometry::Altitude); }
|
||||
void Invalidate();
|
||||
|
||||
std::string m_data;
|
||||
std::vector<uint8_t> m_data;
|
||||
bool m_valid;
|
||||
|
||||
DISALLOW_COPY(SrtmTile);
|
||||
@@ -48,27 +71,35 @@ private:
|
||||
class SrtmTileManager
|
||||
{
|
||||
public:
|
||||
explicit SrtmTileManager(std::string const & dir);
|
||||
|
||||
geometry::Altitude GetHeight(ms::LatLon const & coord);
|
||||
explicit SrtmTileManager(std::string const & dir) : m_dir(dir) {}
|
||||
|
||||
SrtmTile const & GetTile(ms::LatLon const & coord);
|
||||
|
||||
private:
|
||||
using LatLonKey = std::pair<int32_t, int32_t>;
|
||||
static LatLonKey GetKey(ms::LatLon const & coord);
|
||||
geometry::Altitude GetAltitude(ms::LatLon const & coord)
|
||||
{
|
||||
return GetTile(coord).GetAltitude(coord);
|
||||
}
|
||||
|
||||
size_t GeTilesNumber() const { return m_tiles.size(); }
|
||||
|
||||
void Purge();
|
||||
|
||||
private:
|
||||
std::string m_dir;
|
||||
|
||||
struct Hash
|
||||
{
|
||||
size_t operator()(LatLonKey const & key) const
|
||||
size_t operator()(SrtmTile::LatLonKey const & key) const
|
||||
{
|
||||
return (static_cast<size_t>(key.first) << 32u) | static_cast<size_t>(key.second);
|
||||
size_t seed = 0;
|
||||
boost::hash_combine(seed, key.first);
|
||||
boost::hash_combine(seed, key.second);
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
|
||||
std::unordered_map<LatLonKey, SrtmTile, Hash> m_tiles;
|
||||
using MapT = std::unordered_map<SrtmTile::LatLonKey, SrtmTile, Hash>;
|
||||
MapT m_tiles;
|
||||
|
||||
DISALLOW_COPY(SrtmTileManager);
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <vector>
|
||||
|
||||
#define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED
|
||||
#define BOOST_STACKTRACE_USE_ADDR2LINE
|
||||
#include <boost/stacktrace.hpp>
|
||||
|
||||
namespace generator
|
||||
|
||||
@@ -55,10 +55,10 @@ void WikiUrlDumper::Dump(size_t cpuCount) const
|
||||
// static
|
||||
void WikiUrlDumper::DumpOne(std::string const & path, std::ostream & stream)
|
||||
{
|
||||
auto const & needWikiUrl = ftypes::AttractionsChecker::Instance();
|
||||
//auto const & needWikiUrl = ftypes::AttractionsChecker::Instance();
|
||||
feature::ForEachFeatureRawFormat(path, [&](FeatureBuilder const & feature, uint64_t /* pos */) {
|
||||
if (!needWikiUrl(feature.GetTypesHolder()))
|
||||
return;
|
||||
// if (!needWikiUrl(feature.GetTypesHolder()))
|
||||
// return;
|
||||
|
||||
auto const wikiUrl = feature.GetMetadata().GetWikiURL();
|
||||
if (wikiUrl.empty())
|
||||
@@ -89,10 +89,10 @@ WikiDataFilter::WikiDataFilter(std::string const & path, std::vector<std::string
|
||||
void WikiDataFilter::FilterOne(std::string const & path, std::map<base::GeoObjectId, std::string> const & idToWikiData,
|
||||
std::ostream & stream)
|
||||
{
|
||||
auto const & needWikiUrl = ftypes::AttractionsChecker::Instance();
|
||||
//auto const & needWikiUrl = ftypes::AttractionsChecker::Instance();
|
||||
feature::ForEachFeatureRawFormat(path, [&](FeatureBuilder const & feature, uint64_t /* pos */) {
|
||||
if (!needWikiUrl(feature.GetTypesHolder()))
|
||||
return;
|
||||
// if (!needWikiUrl(feature.GetTypesHolder()))
|
||||
// return;
|
||||
|
||||
auto const it = idToWikiData.find(feature.GetMostGenericOsmId());
|
||||
if (it == std::end(idToWikiData))
|
||||
|
||||
@@ -62,8 +62,12 @@ UNIT_TEST(Segment_Smoke)
|
||||
TEST(!OnSegment(P(10 + eps, 10), ps), ());
|
||||
TEST(!OnSegment(P(0, 0), ps), ());
|
||||
}
|
||||
}
|
||||
|
||||
// Paranoid tests.
|
||||
// This paranoid test doesn' work with Release optimizations (LTO?).
|
||||
#ifndef NDEBUG
|
||||
UNIT_TEST(Segment_Paranoid)
|
||||
{
|
||||
{
|
||||
P ps[] = {{0, 0}, {1e100, 1e100}};
|
||||
TEST(OnSegment(ps[0], ps), ());
|
||||
@@ -94,6 +98,7 @@ UNIT_TEST(Segment_Smoke)
|
||||
TEST(!OnSegment(P(1e-16, 2.0 * 1e-16), ps), ());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
UNIT_TEST(Triangle_Smoke)
|
||||
{
|
||||
@@ -129,6 +134,8 @@ UNIT_TEST(Triangle_PointInsideSegment)
|
||||
TEST(!InsideTriangle(P(eps, eps), ps), ());
|
||||
}
|
||||
|
||||
// This paranoid test doesn' work with Release optimizations (LTO?).
|
||||
#ifndef NDEBUG
|
||||
UNIT_TEST(Triangle_PointInsidePoint)
|
||||
{
|
||||
double constexpr eps = 1.0E-10;
|
||||
@@ -147,6 +154,7 @@ UNIT_TEST(Triangle_PointInsidePoint)
|
||||
TEST(!InsideTriangle(P(eps, eps), ps), ());
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
UNIT_TEST(PolygonSelfIntersections_IntersectSmoke)
|
||||
{
|
||||
|
||||
@@ -12,15 +12,38 @@ using namespace std;
|
||||
using m2::Spline;
|
||||
using m2::PointD;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
double constexpr kAlmostZero = 1.0E-16;
|
||||
|
||||
void TestEqual(double x, double y)
|
||||
{
|
||||
if (fabs(x) < kAlmostZero || fabs(y) < kAlmostZero)
|
||||
TEST_ALMOST_EQUAL_ABS(x, y, kAlmostZero, ());
|
||||
else
|
||||
TEST_ALMOST_EQUAL_ULPS(x, y, ());
|
||||
}
|
||||
|
||||
void TestPointDDir(PointD const & dst, PointD const & src)
|
||||
{
|
||||
double const len1 = dst.Length();
|
||||
double const len2 = src.Length();
|
||||
TEST_ALMOST_EQUAL_ULPS(dst.x/len1, src.x/len2, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(dst.y/len1, src.y/len2, ());
|
||||
if (len1 < kAlmostZero || len2 < kAlmostZero)
|
||||
{
|
||||
TestEqual(dst.x, src.x);
|
||||
TestEqual(dst.y, src.y);
|
||||
}
|
||||
else
|
||||
{
|
||||
TestEqual(dst.x/len1, src.x/len2);
|
||||
TestEqual(dst.y/len1, src.y/len2);
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(SmoothedDirections)
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(Spline_SmoothedDirections)
|
||||
{
|
||||
vector<PointD> path;
|
||||
path.push_back(PointD(0, 0));
|
||||
|
||||
@@ -157,6 +157,7 @@ public:
|
||||
}
|
||||
|
||||
Container const & Data() const { return m_points; }
|
||||
Container & MutableData() { return m_points; }
|
||||
|
||||
template <typename EqualFn>
|
||||
static bool IsIntersect(Coord const & x11, Coord const & y11, Coord const & x12,
|
||||
|
||||
@@ -77,7 +77,7 @@ class Altitudes
|
||||
public:
|
||||
Altitudes() = default;
|
||||
|
||||
explicit Altitudes(geometry::Altitudes const & altitudes) : m_altitudes(altitudes) {}
|
||||
explicit Altitudes(geometry::Altitudes && altitudes) : m_altitudes(std::move(altitudes)) {}
|
||||
|
||||
template <class TSink>
|
||||
void Serialize(geometry::Altitude minAltitude, TSink & sink) const
|
||||
|
||||
@@ -7,20 +7,25 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace feature
|
||||
{
|
||||
template <class ToDo>
|
||||
void ForEachFeature(ModelReaderPtr const & reader, ToDo && toDo)
|
||||
void ForEachFeature(FilesContainerR const & cont, ToDo && toDo)
|
||||
{
|
||||
FeaturesVectorTest features((FilesContainerR(reader)));
|
||||
features.GetVector().ForEach(std::forward<ToDo>(toDo));
|
||||
FeaturesVectorTest features(cont);
|
||||
features.GetVector().ForEach(toDo);
|
||||
}
|
||||
|
||||
template <class ToDo>
|
||||
void ForEachFeature(ModelReaderPtr reader, ToDo && toDo)
|
||||
{
|
||||
ForEachFeature(FilesContainerR(reader), toDo);
|
||||
}
|
||||
|
||||
template <class ToDo>
|
||||
void ForEachFeature(std::string const & fPath, ToDo && toDo)
|
||||
{
|
||||
ForEachFeature(std::make_unique<FileReader>(fPath), std::forward<ToDo>(toDo));
|
||||
ForEachFeature(std::make_unique<FileReader>(fPath), toDo);
|
||||
}
|
||||
} // namespace feature
|
||||
|
||||
@@ -1488,6 +1488,6 @@
|
||||
"type.barrier.guard_rail" = "Leitplanke";
|
||||
"type.highway.ladder" = "Leiter";
|
||||
"type.landuse.plant_nursery" = "Gartenbau";
|
||||
"type.leisure.firepit" = "Feuerschale";
|
||||
"type.leisure.firepit" = "Feuerstelle";
|
||||
"type.amenity.studio" = "Studio";
|
||||
"type.sport.diving" = "Tauchen";
|
||||
|
||||
@@ -1484,3 +1484,10 @@
|
||||
|
||||
/* https://wiki.openstreetmap.org/wiki/Tag:leisure=sports_hall */
|
||||
"type.leisure.sports_hall" = "Pabellón deportivo";
|
||||
"type.amenity.studio" = "Estudio";
|
||||
"type.leisure.firepit" = "Fogón";
|
||||
"type.amenity.love_hotel" = "Hotel del amor";
|
||||
"type.barrier.guard_rail" = "Guardarraíl";
|
||||
"type.highway.ladder" = "Escalera";
|
||||
"type.landuse.plant_nursery" = "Vivero de plantas";
|
||||
"type.sport.diving" = "Buceo";
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"type.amenity.bicycle_parking" = "Polkupyöräpysäköinti";
|
||||
"type.amenity.bicycle_rental" = "Polkupyöränvuokraus";
|
||||
"type.amenity.bicycle_repair_station" = "Polkupyörän korjausasema";
|
||||
"type.amenity.biergarten" = "Biergarten";
|
||||
"type.amenity.biergarten" = "Olutpuutarha";
|
||||
"type.amenity.brothel" = "Bordelli";
|
||||
"type.amenity.bureau_de_change" = "Rahanvaihto";
|
||||
"type.amenity.bus_station" = "Linja-autoasema";
|
||||
@@ -179,7 +179,7 @@
|
||||
"type.amenity.vending_machine.parking_tickets" = "Pysäköintimaksuautomaatti";
|
||||
"type.amenity.vending_machine.public_transport_tickets" = "Julkisen liikenteen lippuautomaatti";
|
||||
"type.amenity.vending_machine.sweets" = "Makeisautomaatti";
|
||||
"type.amenity.vending_machine.excrement_bags" = "Excrement Bags Dispenser";
|
||||
"type.amenity.vending_machine.excrement_bags" = "Koirankakkapussiautomaatti";
|
||||
"type.amenity.parcel_locker" = "Pakettilokero";
|
||||
"type.amenity.vehicle_inspection" = "Ajoneuvon tarkastus";
|
||||
"type.amenity.vending_machine.fuel" = "Bensa-automaatti";
|
||||
@@ -1486,3 +1486,8 @@
|
||||
"type.leisure.sports_hall" = "Urheiluhalli";
|
||||
"type.amenity.love_hotel" = "Rakkaushotelli";
|
||||
"type.sport.diving" = "Sukellus";
|
||||
"type.amenity.studio" = "Studio";
|
||||
"type.barrier.guard_rail" = "Tiekaide";
|
||||
"type.highway.ladder" = "Tikapuut";
|
||||
"type.landuse.plant_nursery" = "Kasvitarha";
|
||||
"type.leisure.firepit" = "Tulentekopaikka";
|
||||
|
||||
@@ -143,7 +143,7 @@
|
||||
"type.recycling.cans" = "缶";
|
||||
"type.recycling.shoes" = "靴";
|
||||
"type.recycling.green_waste" = "有機性廃棄物";
|
||||
"type.recycling.cartons" = "Cartons";
|
||||
"type.recycling.cartons" = "紙箱";
|
||||
"type.amenity.restaurant" = "レストラン";
|
||||
"type.amenity.sanitary_dump_station" = "ダンプステーション";
|
||||
"type.amenity.school" = "学校";
|
||||
@@ -836,8 +836,8 @@
|
||||
"type.office.lawyer" = "弁護士事務所";
|
||||
"type.office.ngo" = "NGO 事務局";
|
||||
"type.office.telecommunication" = "携帯電話事業者";
|
||||
"type.organic.only" = "Organic";
|
||||
"type.organic.yes" = "Organic";
|
||||
"type.organic.only" = "有機";
|
||||
"type.organic.yes" = "有機";
|
||||
"type.place.city" = "市";
|
||||
"type.place.city.capital" = "首都";
|
||||
"type.place.city.capital.10" = "市";
|
||||
@@ -898,7 +898,7 @@
|
||||
"type.power.tower" = "電柱";
|
||||
|
||||
/* A single pole supporting minor power lines. */
|
||||
"type.power.pole" = "Power Pole";
|
||||
"type.power.pole" = "電柱";
|
||||
|
||||
/* A single pole supporting various public utilities, such as lighting or telephony. */
|
||||
"type.man_made.utility_pole" = "Utility Pole";
|
||||
|
||||
@@ -647,7 +647,6 @@
|
||||
"type.hwtag" = "hwtag";
|
||||
"type.hwtag.bidir_bicycle" = "hwtag-bidir_bicycle";
|
||||
"type.hwtag.onedir_bicycle" = "hwtag-onedir_bicycle";
|
||||
"type.hwtag.lit" = "hwtag-lit";
|
||||
"type.hwtag.nobicycle" = "hwtag-nobicycle";
|
||||
"type.hwtag.nocar" = "hwtag-nocar";
|
||||
"type.hwtag.nofoot" = "hwtag-nofoot";
|
||||
@@ -742,7 +741,7 @@
|
||||
"type.man_made.cairn" = "Höyük";
|
||||
"type.man_made.chimney" = "Fabrika Bacası";
|
||||
"type.man_made.cutline" = "Kesme Çizgisi";
|
||||
"type.man_made.survey_point" = "Survey Point";
|
||||
"type.man_made.survey_point" = "Anket Noktası";
|
||||
"type.man_made.flagpole" = "Bayrak Direği";
|
||||
"type.man_made.lighthouse" = "Deniz Feneri";
|
||||
"type.man_made.mast" = "Direk";
|
||||
@@ -1484,3 +1483,5 @@
|
||||
|
||||
/* https://wiki.openstreetmap.org/wiki/Tag:leisure=sports_hall */
|
||||
"type.leisure.sports_hall" = "Spor salonu";
|
||||
"type.amenity.studio" = "Stüdyo";
|
||||
"type.sport.diving" = "Dalış Sporu";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user