mirror of
https://codeberg.org/comaps/comaps
synced 2026-01-21 18:53:51 +00:00
Compare commits
10 Commits
zy-mwm-int
...
zy-subway-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0941c7b2bd | ||
|
|
e714138ce7 | ||
|
|
9969508193 | ||
|
|
08daf740e2 | ||
|
|
c34c109abf | ||
|
|
e4c2961a5e | ||
|
|
e156d21eee | ||
|
|
7a3e3159c0 | ||
|
|
64f3defc6d | ||
|
|
b59b3f610f |
@@ -1,13 +1,23 @@
|
||||
name: process_subways
|
||||
name: compare_subways
|
||||
on:
|
||||
workflow_dispatch: # Manual trigger
|
||||
inputs:
|
||||
subways-branch:
|
||||
description: 'Check out a different subways repo branch?'
|
||||
required: true
|
||||
type: string
|
||||
default: master
|
||||
schedule:
|
||||
# Run daily at 00:00 UTC
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
env:
|
||||
PLANET: /home/planet/planet/planet.o5m
|
||||
TMPDIR: /tmp
|
||||
#PLANET: /home/planet/planet/planet-latest.pbf
|
||||
#TMPDIR: /tmp
|
||||
HTML_DIR: "/mnt/4tbexternal/osm-planet/subway/validator"
|
||||
DUMP: "$HTML_DIR"
|
||||
SKIP_PLANET_UPDATE: "1"
|
||||
#DUMP: "$HTML_DIR"
|
||||
#SKIP_PLANET_UPDATE: "1"
|
||||
SPREADSHEET_ID: "1SEW1-NiNOnA2qDwievcxYV1FOaQl1mb1fdeyqAxHu3k"
|
||||
DEBIAN_FRONTEND: nonnteractive
|
||||
TZ: Etc/UTC
|
||||
|
||||
@@ -22,20 +32,61 @@ jobs:
|
||||
volumes:
|
||||
- /mnt/4tbexternal:/mnt/4tbexternal
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-process-subways-${{ github.event.pull_request.number || github.ref }}
|
||||
group: ${{ github.workflow }}-compare-subways-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: "~"
|
||||
key: cache-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
key: cache-${{ github.run_id }}-${{ github.run_attempt }}}
|
||||
- name: Checkout main repo
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Cloning $FORGEJO_SERVER_URL/$FORGEJO_REPOSITORY branch $FORGEJO_REF_NAME"
|
||||
cd ~
|
||||
git clone --depth 1 -b $FORGEJO_REF_NAME --single-branch $FORGEJO_SERVER_URL/$FORGEJO_REPOSITORY.git comaps
|
||||
# --recurse-submodules --shallow-submodules
|
||||
- name: Checkout subways repo
|
||||
shell: bash
|
||||
run: |
|
||||
cd ~
|
||||
git clone --depth 1 --single-branch https://codeberg.org/comaps/subways.git
|
||||
git clone --depth 1 -b ${{ inputs.subways-branch }} --single-branch https://codeberg.org/comaps/subways.git
|
||||
|
||||
update-planet-pbf:
|
||||
name: Update PBF Planet
|
||||
runs-on: mapfilemaker
|
||||
container:
|
||||
image: codeberg.org/comaps/maps_generator:f6d53d54f794
|
||||
volumes:
|
||||
- /mnt/4tbexternal/:/mnt/4tbexternal/
|
||||
- /mnt/4tbexternal/osm-planet:/home/planet
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-compare-subways-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Download Planet File if Absent
|
||||
shell: bash
|
||||
# TODO: replace wget2 with curl -Z
|
||||
run: |
|
||||
if [ ! -d /home/planet/planet/ ]; then
|
||||
mkdir -p /home/planet/planet/
|
||||
fi
|
||||
if [ ! -f /home/planet/planet/planet-latest.osm.pbf ]; then
|
||||
cd /home/planet/planet/
|
||||
wget2 --verbose --progress=bar --continue https://ftpmirror.your.org/pub/openstreetmap/pbf/planet-latest.osm.pbf
|
||||
else
|
||||
echo "planet-latest.osm.pbf was found, raw download not required."
|
||||
fi
|
||||
- name: Update PBF Planet
|
||||
shell: bash
|
||||
run: |
|
||||
cd /home/planet/planet/
|
||||
rm -f planet-latest-new.osm.pbf
|
||||
pyosmium-up-to-date planet-latest.osm.pbf -o planet-latest-new.osm.pbf -v --size 16384
|
||||
mv planet-latest-new.osm.pbf planet-latest.osm.pbf
|
||||
|
||||
update-planet-o5m:
|
||||
if: false
|
||||
name: Update O5M Planet
|
||||
runs-on: mapfilemaker
|
||||
container:
|
||||
@@ -44,7 +95,7 @@ jobs:
|
||||
- /mnt/4tbexternal/:/mnt/4tbexternal/
|
||||
- /mnt/4tbexternal/osm-planet:/home/planet
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-process-subways-${{ github.event.pull_request.number || github.ref }}
|
||||
group: ${{ github.workflow }}-compare-subways-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Check for O5M Planet File
|
||||
@@ -73,18 +124,9 @@ jobs:
|
||||
osmupdate -v --drop-author --drop-version --hash-memory=4000 --max-merge=32 --out-o5m planet.o5m planet-new.o5m
|
||||
mv planet-new.o5m planet.o5m
|
||||
echo "Done."
|
||||
- name: Notify Zulip
|
||||
run: |
|
||||
curl -X POST https://comaps.zulipchat.com/api/v1/messages \
|
||||
-u $ZULIP_BOT_EMAIL:$ZULIP_API_KEY \
|
||||
--data-urlencode type=stream \
|
||||
--data-urlencode 'to="DevOps"' \
|
||||
--data-urlencode topic=codeberg-bot \
|
||||
--data-urlencode 'content=O5M planet update is done!'
|
||||
|
||||
update-subways:
|
||||
if: inputs.run-subways
|
||||
name: Update Subways
|
||||
|
||||
compare-subways:
|
||||
name: Compare Subways
|
||||
runs-on: mapfilemaker
|
||||
needs:
|
||||
- clone-repos
|
||||
@@ -94,31 +136,19 @@ jobs:
|
||||
- /mnt/4tbexternal/:/mnt/4tbexternal/
|
||||
- /mnt/4tbexternal/osm-planet:/home/planet
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-map-generator-${{ github.event.pull_request.number || github.ref }}
|
||||
group: ${{ github.workflow }}-compare-subways-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: "~"
|
||||
key: cache-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
- name: Update Subways
|
||||
- name: Compare Subways
|
||||
shell: bash
|
||||
run: |
|
||||
cd ~/comaps/
|
||||
cp tools/unix/maps/settings.sh.prod tools/unix/maps/settings.sh
|
||||
./tools/unix/maps/generate_subways.sh
|
||||
- name: Compare with VK validation
|
||||
shell: bash
|
||||
run: |
|
||||
cd ~/subways
|
||||
if [ -f "$HTML_DIR/index.html" ]; then
|
||||
echo "Comparing local validation with VK's validation..."
|
||||
python3 ./scripts/compare_html_validation.py "$HTML_DIR/index.html" \
|
||||
--vk-url "https://maps.vk.com/osm/tools/subways/latest/index.html"
|
||||
else
|
||||
echo "Local index.html not found at $HTML_DIR/index.html"
|
||||
exit 1
|
||||
fi
|
||||
- name: Notify Zulip
|
||||
run: |
|
||||
curl -X POST https://comaps.zulipchat.com/api/v1/messages \
|
||||
@@ -127,3 +157,48 @@ jobs:
|
||||
--data-urlencode 'to="DevOps"' \
|
||||
--data-urlencode topic=codeberg-bot \
|
||||
--data-urlencode 'content=Subways are done!'
|
||||
- name: Compare with VK validation
|
||||
shell: bash
|
||||
run: |
|
||||
#set -e
|
||||
cd ~/subways
|
||||
if [ -f "$HTML_DIR/cities.txt" ]; then
|
||||
echo "Comparing local validation with VK's validation..."
|
||||
python3 ./scripts/compare_html_validation.py "$HTML_DIR/cities.txt" \
|
||||
--remote-url "https://maps.vk.com/osm/tools/subways/latest/cities.txt"
|
||||
exit 0
|
||||
else
|
||||
echo "Local cities.txt not found at $HTML_DIR/cities.txt"
|
||||
exit 1
|
||||
fi
|
||||
- name: Compare Google Sheets Data
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
|
||||
# Download the Google Sheets data
|
||||
GOOGLE_SHEETS_URL="https://docs.google.com/spreadsheets/d/${SPREADSHEET_ID}/export?format=csv"
|
||||
echo "Downloading Google Sheets data from: $GOOGLE_SHEETS_URL"
|
||||
curl -sL "$GOOGLE_SHEETS_URL" -o /tmp/google_sheets.csv
|
||||
|
||||
# Normalize line endings to avoid spurious diffs
|
||||
sed 's/\r$//' source_data/Rapid.csv > /tmp/local_normalized.csv
|
||||
sed 's/\r$//' /tmp/google_sheets.csv > /tmp/google_normalized.csv
|
||||
|
||||
# Generate unified diff
|
||||
echo ""
|
||||
echo "Differences between local Rapid.csv and Google Sheets:"
|
||||
echo ""
|
||||
|
||||
if diff -u /tmp/local_normalized.csv /tmp/google_normalized.csv > /tmp/full_diff.txt; then
|
||||
echo "No differences found! Local Rapid.csv is in sync with Google Sheets."
|
||||
exit 0
|
||||
else
|
||||
cat /tmp/full_diff.txt
|
||||
|
||||
echo ""
|
||||
echo "The above diff can be saved as a patch and applied with:"
|
||||
echo " patch source_data/Rapid.csv < patch-file"
|
||||
echo "or by manually reviewing and applying the changes."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -346,60 +346,6 @@
|
||||
<data android:mimeType="text/xml" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Custom MWM map files -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="content"/>
|
||||
<data android:scheme="file"/>
|
||||
<data android:host="*"/>
|
||||
<data android:mimeType="*/*"/>
|
||||
<!-- See http://stackoverflow.com/questions/3400072/pathpattern-to-match-file-extension-does-not-work-if-a-period-exists-elsewhere-i -->
|
||||
<data android:pathPattern="/.*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.MWM" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Duplicate without mimeType for MWM files -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="content"/>
|
||||
<data android:scheme="file"/>
|
||||
<data android:host="*"/>
|
||||
<data android:pathPattern="/.*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.MWM" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
|
||||
@@ -331,7 +331,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
}
|
||||
|
||||
final IntentProcessor[] mIntentProcessors = {
|
||||
new Factory.MwmFileProcessor(),
|
||||
new Factory.UrlProcessor(),
|
||||
new Factory.KmzKmlProcessor(),
|
||||
};
|
||||
|
||||
@@ -473,16 +473,7 @@ class DownloaderAdapter extends RecyclerView.Adapter<DownloaderAdapter.ViewHolde
|
||||
{
|
||||
mName.setText(mItem.name);
|
||||
if (!mItem.isExpandable())
|
||||
{
|
||||
// Show version info for downloaded maps (in "My Maps" mode)
|
||||
String subtitle = mItem.description;
|
||||
if (mMyMapsMode && mItem.present && mItem.localVersion > 0)
|
||||
{
|
||||
String versionStr = formatVersion(mItem.localVersion);
|
||||
subtitle = TextUtils.isEmpty(subtitle) ? "v" + versionStr : subtitle + " • v" + versionStr;
|
||||
}
|
||||
UiUtils.setTextAndHideIfEmpty(mSubtitle, subtitle);
|
||||
}
|
||||
UiUtils.setTextAndHideIfEmpty(mSubtitle, mItem.description);
|
||||
}
|
||||
|
||||
if (mItem.isExpandable())
|
||||
@@ -518,21 +509,6 @@ class DownloaderAdapter extends RecyclerView.Adapter<DownloaderAdapter.ViewHolde
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a version number (YYMMDD) into a readable date string (YY.MM.DD).
|
||||
*/
|
||||
private String formatVersion(long version)
|
||||
{
|
||||
if (version <= 0)
|
||||
return "";
|
||||
String v = String.valueOf(version);
|
||||
// Pad with leading zeros if needed
|
||||
while (v.length() < 6)
|
||||
v = "0" + v;
|
||||
// Format as YY.MM.DD
|
||||
return v.substring(0, 2) + "." + v.substring(2, 4) + "." + v.substring(4, 6);
|
||||
}
|
||||
}
|
||||
|
||||
static class HeaderViewHolder extends BaseInnerViewHolder<String>
|
||||
|
||||
@@ -23,12 +23,10 @@ import app.organicmaps.sdk.routing.RoutingController;
|
||||
import app.organicmaps.sdk.search.SearchEngine;
|
||||
import app.organicmaps.sdk.util.StorageUtils;
|
||||
import app.organicmaps.sdk.util.concurrency.ThreadPool;
|
||||
import app.organicmaps.sdk.downloader.CustomMwmManager;
|
||||
import app.organicmaps.search.SearchActivity;
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class Factory
|
||||
{
|
||||
@@ -67,92 +65,6 @@ public class Factory
|
||||
}
|
||||
}
|
||||
|
||||
public static class MwmFileProcessor implements IntentProcessor
|
||||
{
|
||||
private static final String MWM_EXTENSION = ".mwm";
|
||||
|
||||
@Override
|
||||
public boolean process(@NonNull Intent intent, @NonNull MwmActivity activity)
|
||||
{
|
||||
if (!Intent.ACTION_VIEW.equals(intent.getAction()))
|
||||
return false;
|
||||
|
||||
final Uri uri = intent.getData();
|
||||
if (uri == null)
|
||||
return false;
|
||||
|
||||
// Check if this is an MWM file
|
||||
if (!isMwmFile(activity, uri))
|
||||
return false;
|
||||
|
||||
// Import the MWM file on a background thread
|
||||
ThreadPool.getStorage().execute(() -> {
|
||||
CustomMwmManager.ImportResult result = CustomMwmManager.importMwmFile(activity, uri);
|
||||
|
||||
// Show result on UI thread
|
||||
activity.runOnUiThread(() -> {
|
||||
switch (result)
|
||||
{
|
||||
case SUCCESS:
|
||||
android.widget.Toast.makeText(activity,
|
||||
activity.getString(app.organicmaps.R.string.custom_mwm_import_success),
|
||||
android.widget.Toast.LENGTH_LONG).show();
|
||||
// Reload maps to include the new custom map
|
||||
Framework.nativeReloadWorldMaps();
|
||||
break;
|
||||
case ERROR_INVALID_FILE:
|
||||
android.widget.Toast.makeText(activity,
|
||||
activity.getString(app.organicmaps.R.string.custom_mwm_import_invalid),
|
||||
android.widget.Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case ERROR_IO:
|
||||
case ERROR_STORAGE:
|
||||
android.widget.Toast.makeText(activity,
|
||||
activity.getString(app.organicmaps.R.string.custom_mwm_import_error),
|
||||
android.widget.Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isMwmFile(@NonNull MwmActivity activity, @NonNull Uri uri)
|
||||
{
|
||||
String fileName = null;
|
||||
|
||||
// Try to get filename from content resolver
|
||||
if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()))
|
||||
{
|
||||
try (android.database.Cursor cursor = activity.getContentResolver().query(
|
||||
uri, new String[]{android.provider.OpenableColumns.DISPLAY_NAME}, null, null, null))
|
||||
{
|
||||
if (cursor != null && cursor.moveToFirst())
|
||||
{
|
||||
int nameIndex = cursor.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME);
|
||||
if (nameIndex >= 0)
|
||||
fileName = cursor.getString(nameIndex);
|
||||
}
|
||||
}
|
||||
catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
// Fallback to URI path
|
||||
if (fileName == null)
|
||||
{
|
||||
String path = uri.getPath();
|
||||
if (path != null)
|
||||
{
|
||||
int lastSlash = path.lastIndexOf('/');
|
||||
fileName = lastSlash >= 0 ? path.substring(lastSlash + 1) : path;
|
||||
}
|
||||
}
|
||||
|
||||
return fileName != null && fileName.toLowerCase(Locale.US).endsWith(MWM_EXTENSION);
|
||||
}
|
||||
}
|
||||
|
||||
public static class UrlProcessor implements IntentProcessor
|
||||
{
|
||||
private static final int SEARCH_IN_VIEWPORT_ZOOM = 16;
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
android:width="74dp"
|
||||
android:height="57dp"
|
||||
android:viewportWidth="74"
|
||||
android:viewportHeight="57">
|
||||
android:viewportHeight="57"
|
||||
android:tint="?iconTint">
|
||||
<path
|
||||
android:fillColor="#757575"
|
||||
android:pathData="m26.278,8.137c3.695,-3.729 9.719,-3.761 13.453,-0.073 3.736,3.687 3.769,9.699 0.074,13.425l-14.597,14.726c-2.353,2.373 -2.331,6.197 0.046,8.544 2.379,2.345 6.209,2.326 8.562,-0.048l14.303,-14.428c-0.155,-0.32 -0.286,-0.647 -0.394,-0.978l-4.006,0.001c-2.787,0 -3.659,-3.825 -1.045,-5.042l7.813,-3.604c0.879,-0.652 1.87,-1.098 2.9,-1.333l11.936,-5.495c2.091,-0.869 4.357,1.392 3.486,3.478l-10.454,22.603c-1.219,2.607 -5.053,1.736 -5.053,-1.044v-4.418c-0.506,-0.122 -1.006,-0.296 -1.494,-0.525l-14.303,14.428c-4.368,4.406 -11.485,4.444 -15.901,0.086 -4.413,-4.358 -4.452,-11.461 -0.084,-15.867l14.597,-14.726c1.68,-1.693 1.665,-4.426 -0.033,-6.102 -1.7,-1.677 -4.437,-1.662 -6.117,0.033l-9.43,9.516c0.436,0.994 0.679,2.091 0.679,3.244 0,4.464 -3.634,8.091 -8.108,8.091 -4.475,0 -8.108,-3.627 -8.108,-8.091 0,-4.466 3.633,-8.091 8.108,-8.091 1.424,0 2.764,0.367 3.928,1.012z" />
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="m26.278,8.137c3.695,-3.729 9.719,-3.761 13.453,-0.073 3.736,3.687 3.769,9.699 0.074,13.425L25.208,36.215c-2.353,2.373 -2.331,6.197 0.046,8.544 2.379,2.345 6.209,2.326 8.562,-0.048L48.119,30.283c-0.155,-0.32 -0.286,-0.647 -0.394,-0.978l-4.006,0.001c-2.787,0 -3.659,-3.825 -1.045,-5.042L65.323,13.832c2.091,-0.869 4.357,1.392 3.486,3.478L58.355,39.913c-1.219,2.607 -5.053,1.736 -5.053,-1.044V34.451c-0.506,-0.122 -1.006,-0.296 -1.494,-0.525L37.505,48.354C33.137,52.76 26.02,52.798 21.604,48.44 17.191,44.082 17.152,36.979 21.52,32.573L36.117,17.847c1.68,-1.693 1.665,-4.426 -0.033,-6.102 -1.7,-1.677 -4.437,-1.662 -6.117,0.033l-9.43,9.516c0.436,0.994 0.679,2.091 0.679,3.244 0,4.464 -3.634,8.091 -8.108,8.091C8.633,32.629 5,29.002 5,24.538c0,-4.466 3.633,-8.091 8.108,-8.091 1.424,0 2.764,0.367 3.928,1.012z" />
|
||||
</vector>
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
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="@color/active_track_recording"
|
||||
android:fillColor="@color/base_red"
|
||||
android:fillAlpha="0.78" />
|
||||
<path
|
||||
android:pathData="m33.024,19.508c3.955,-3.981 10.402,-4.015 14.399,-0.078 3.999,3.937 4.034,10.355 0.079,14.333l-15.623,15.722c-2.518,2.534 -2.495,6.616 0.049,9.122 2.546,2.504 6.646,2.483 9.164,-0.051l15.309,-15.404c-0.166,-0.342 -0.306,-0.691 -0.422,-1.044l-4.288,0.001c-2.983,0 -3.916,-4.084 -1.118,-5.383l8.362,-3.848c0.941,-0.696 2.001,-1.172 3.104,-1.423l12.775,-5.867c2.238,-0.928 4.663,1.486 3.731,3.713l-11.189,24.132c-1.305,2.783 -5.408,1.854 -5.408,-1.115l-0,-4.717c-0.542,-0.13 -1.077,-0.316 -1.599,-0.561l-15.309,15.404c-4.675,4.704 -12.293,4.745 -17.019,0.092 -4.723,-4.653 -4.765,-12.237 -0.09,-16.941l15.623,-15.722c1.798,-1.808 1.782,-4.725 -0.035,-6.515 -1.819,-1.79 -4.749,-1.774 -6.547,0.035l-10.093,10.16c0.467,1.061 0.727,2.232 0.727,3.464 0,4.766 -3.89,8.638 -8.678,8.638 -4.79,0 -8.678,-3.872 -8.678,-8.638 0,-4.768 3.888,-8.638 8.678,-8.638 1.524,0 2.958,0.392 4.204,1.081z"
|
||||
android:pathData="m33.024,19.508c3.955,-3.981 10.402,-4.015 14.399,-0.078 3.999,3.937 4.034,10.355 0.079,14.333L31.879,49.485c-2.518,2.534 -2.495,6.616 0.049,9.122 2.546,2.504 6.646,2.483 9.164,-0.051L56.401,43.152c-0.166,-0.342 -0.306,-0.691 -0.422,-1.044l-4.288,0.001c-2.983,0 -3.916,-4.084 -1.118,-5.383L74.814,25.588c2.238,-0.928 4.663,1.486 3.731,3.713L67.356,53.433c-1.305,2.783 -5.408,1.854 -5.408,-1.115V47.601c-0.542,-0.13 -1.077,-0.316 -1.599,-0.561L45.04,62.444C40.365,67.148 32.747,67.189 28.021,62.536 23.298,57.883 23.256,50.299 27.931,45.595L43.554,29.873c1.798,-1.808 1.782,-4.725 -0.035,-6.515 -1.819,-1.79 -4.749,-1.774 -6.547,0.035l-10.093,10.16c0.467,1.061 0.727,2.232 0.727,3.464 0,4.766 -3.89,8.638 -8.678,8.638 -4.79,0 -8.678,-3.872 -8.678,-8.638 0,-4.768 3.888,-8.638 8.678,-8.638 1.524,0 2.958,0.392 4.204,1.081z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
android:tint="?iconTint"
|
||||
app:srcCompat="@drawable/ic_plus"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearance.MapButton.Zoom.Minus"
|
||||
android:layout_marginBottom="@dimen/margin_eighth"
|
||||
android:layout_marginBottom="@dimen/margin_eighth_plus"
|
||||
android:contentDescription="@string/zoom_in"/>
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/nav_zoom_out"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
<!-- margins -->
|
||||
<dimen name="margin_eighth">2dp</dimen>
|
||||
<dimen name="margin_eighth_plus">3dp</dimen>
|
||||
<dimen name="margin_quarter">4dp</dimen>
|
||||
<dimen name="margin_quarter_plus">6dp</dimen>
|
||||
<dimen name="margin_half">8dp</dimen>
|
||||
|
||||
@@ -975,8 +975,4 @@
|
||||
<string name="download_resources_custom_url_message">Override the default map download server used for map downloads. Leave empty to use CoMaps default server.</string>
|
||||
<string name="download_resources_custom_url_summary_none">Not set</string>
|
||||
<string name="download_resources_custom_url_error_scheme">Please enter a URL starting with http:// or https://</string>
|
||||
<!-- Custom MWM file import messages -->
|
||||
<string name="custom_mwm_import_success">Custom map imported successfully. Restart the app to load it.</string>
|
||||
<string name="custom_mwm_import_invalid">Invalid MWM file. Please select a valid map file.</string>
|
||||
<string name="custom_mwm_import_error">Failed to import the map file. Please try again.</string>
|
||||
</resources>
|
||||
|
||||
@@ -67,8 +67,8 @@ struct CountryItemBuilder
|
||||
jclass m_class;
|
||||
jmethodID m_ctor;
|
||||
jfieldID m_Id, m_Name, m_DirectParentId, m_TopmostParentId, m_DirectParentName, m_TopmostParentName, m_Description,
|
||||
m_Size, m_EnqueuedSize, m_TotalSize, m_LocalVersion, m_ChildCount, m_TotalChildCount, m_Present, m_Progress,
|
||||
m_DownloadedBytes, m_BytesToDownload, m_Category, m_Status, m_ErrorCode;
|
||||
m_Size, m_EnqueuedSize, m_TotalSize, m_ChildCount, m_TotalChildCount, m_Present, m_Progress, m_DownloadedBytes,
|
||||
m_BytesToDownload, m_Category, m_Status, m_ErrorCode;
|
||||
|
||||
CountryItemBuilder(JNIEnv * env)
|
||||
{
|
||||
@@ -85,7 +85,6 @@ struct CountryItemBuilder
|
||||
m_Size = env->GetFieldID(m_class, "size", "J");
|
||||
m_EnqueuedSize = env->GetFieldID(m_class, "enqueuedSize", "J");
|
||||
m_TotalSize = env->GetFieldID(m_class, "totalSize", "J");
|
||||
m_LocalVersion = env->GetFieldID(m_class, "localVersion", "J");
|
||||
m_ChildCount = env->GetFieldID(m_class, "childCount", "I");
|
||||
m_TotalChildCount = env->GetFieldID(m_class, "totalChildCount", "I");
|
||||
m_Present = env->GetFieldID(m_class, "present", "Z");
|
||||
@@ -222,9 +221,6 @@ static void UpdateItem(JNIEnv * env, jobject item, storage::NodeAttrs const & at
|
||||
env->SetLongField(item, ciBuilder.m_EnqueuedSize, attrs.m_downloadingMwmSize);
|
||||
env->SetLongField(item, ciBuilder.m_TotalSize, attrs.m_mwmSize);
|
||||
|
||||
// Local version (YYMMDD format)
|
||||
env->SetLongField(item, ciBuilder.m_LocalVersion, attrs.m_localMwmVersion);
|
||||
|
||||
// Child counts
|
||||
env->SetIntField(item, ciBuilder.m_ChildCount, attrs.m_downloadingMwmCounter);
|
||||
env->SetIntField(item, ciBuilder.m_TotalChildCount, attrs.m_mwmCounter);
|
||||
|
||||
@@ -53,9 +53,6 @@ public final class CountryItem implements Comparable<CountryItem>
|
||||
public long enqueuedSize;
|
||||
public long totalSize;
|
||||
|
||||
// Local MWM file version (YYMMDD format, e.g. 251231). 0 if not downloaded.
|
||||
public long localVersion;
|
||||
|
||||
public int childCount;
|
||||
public int totalChildCount;
|
||||
|
||||
@@ -158,7 +155,7 @@ public final class CountryItem implements Comparable<CountryItem>
|
||||
+ "\", category: \"" + category + "\", name: \"" + name + "\", directParentName: \"" + directParentName
|
||||
+ "\", topmostParentName: \"" + topmostParentName + "\", present: " + present + ", status: " + status
|
||||
+ ", errorCode: " + errorCode + ", headerId: " + headerId + ", size: " + size + ", enqueuedSize: " + enqueuedSize
|
||||
+ ", totalSize: " + totalSize + ", localVersion: " + localVersion + ", childCount: " + childCount
|
||||
+ ", totalChildCount: " + totalChildCount + ", progress: " + StringUtils.formatUsingUsLocale("%.2f", progress) + "% }";
|
||||
+ ", totalSize: " + totalSize + ", childCount: " + childCount + ", totalChildCount: " + totalChildCount
|
||||
+ ", progress: " + StringUtils.formatUsingUsLocale("%.2f", progress) + "% }";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,359 +0,0 @@
|
||||
package app.organicmaps.sdk.downloader;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.organicmaps.sdk.Framework;
|
||||
import app.organicmaps.sdk.util.StorageUtils;
|
||||
import app.organicmaps.sdk.util.log.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Manages custom MWM map files that are manually imported by the user.
|
||||
* Custom maps are stored in dated folders (YYMMDD format) and take precedence
|
||||
* over downloaded maps when loading.
|
||||
*/
|
||||
public class CustomMwmManager
|
||||
{
|
||||
private static final String TAG = CustomMwmManager.class.getSimpleName();
|
||||
private static final String CUSTOM_MAPS_DIR = "custom_maps";
|
||||
private static final String MWM_EXTENSION = ".mwm";
|
||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyMMdd", Locale.US);
|
||||
|
||||
public enum ImportResult
|
||||
{
|
||||
SUCCESS,
|
||||
ERROR_INVALID_FILE,
|
||||
ERROR_IO,
|
||||
ERROR_STORAGE
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a custom MWM file with its metadata.
|
||||
*/
|
||||
public static class CustomMwmFile
|
||||
{
|
||||
public final String name; // e.g., "Portland"
|
||||
public final String path; // Full path to the file
|
||||
public final long version; // Date version as YYMMDD number
|
||||
public final long fileSize;
|
||||
|
||||
public CustomMwmFile(String name, String path, long version, long fileSize)
|
||||
{
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
this.version = version;
|
||||
this.fileSize = fileSize;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the custom maps root directory path.
|
||||
*/
|
||||
@NonNull
|
||||
public static String getCustomMapsDir(@NonNull Context context)
|
||||
{
|
||||
String writableDir = Framework.nativeGetWritableDir();
|
||||
return StorageUtils.addTrailingSeparator(writableDir) + CUSTOM_MAPS_DIR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or creates the custom maps directory for today's date.
|
||||
* @return The directory path, or null if creation failed.
|
||||
*/
|
||||
@Nullable
|
||||
public static String getTodayCustomMapsDir(@NonNull Context context)
|
||||
{
|
||||
String customMapsDir = getCustomMapsDir(context);
|
||||
String today = DATE_FORMAT.format(new Date());
|
||||
String todayDir = StorageUtils.addTrailingSeparator(customMapsDir) + today;
|
||||
|
||||
File dir = new File(todayDir);
|
||||
if (!dir.exists() && !dir.mkdirs())
|
||||
{
|
||||
Logger.e(TAG, "Failed to create custom maps directory: " + todayDir);
|
||||
return null;
|
||||
}
|
||||
|
||||
return todayDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports an MWM file from a content URI into the custom maps directory.
|
||||
* The file is saved in a dated folder based on today's date.
|
||||
*
|
||||
* @param context Application context
|
||||
* @param uri Content URI of the MWM file
|
||||
* @return ImportResult indicating success or the type of error
|
||||
*/
|
||||
@NonNull
|
||||
public static ImportResult importMwmFile(@NonNull Context context, @NonNull Uri uri)
|
||||
{
|
||||
String fileName = getFileNameFromUri(context, uri);
|
||||
if (fileName == null || !fileName.toLowerCase(Locale.US).endsWith(MWM_EXTENSION))
|
||||
{
|
||||
Logger.e(TAG, "Invalid file name or not an MWM file: " + fileName);
|
||||
return ImportResult.ERROR_INVALID_FILE;
|
||||
}
|
||||
|
||||
String destDir = getTodayCustomMapsDir(context);
|
||||
if (destDir == null)
|
||||
{
|
||||
return ImportResult.ERROR_STORAGE;
|
||||
}
|
||||
|
||||
File destFile = new File(destDir, fileName);
|
||||
Logger.i(TAG, "Importing MWM file to: " + destFile.getAbsolutePath());
|
||||
|
||||
try
|
||||
{
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
if (!StorageUtils.copyFile(resolver, uri, destFile))
|
||||
{
|
||||
Logger.e(TAG, "Failed to copy MWM file");
|
||||
return ImportResult.ERROR_IO;
|
||||
}
|
||||
|
||||
Logger.i(TAG, "Successfully imported MWM file: " + fileName);
|
||||
return ImportResult.SUCCESS;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Logger.e(TAG, "IOException while importing MWM file", e);
|
||||
return ImportResult.ERROR_IO;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file name from a content URI.
|
||||
*/
|
||||
@Nullable
|
||||
private static String getFileNameFromUri(@NonNull Context context, @NonNull Uri uri)
|
||||
{
|
||||
String fileName = null;
|
||||
|
||||
if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()))
|
||||
{
|
||||
try (android.database.Cursor cursor = context.getContentResolver().query(
|
||||
uri, new String[]{android.provider.OpenableColumns.DISPLAY_NAME}, null, null, null))
|
||||
{
|
||||
if (cursor != null && cursor.moveToFirst())
|
||||
{
|
||||
int nameIndex = cursor.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME);
|
||||
if (nameIndex >= 0)
|
||||
{
|
||||
fileName = cursor.getString(nameIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.e(TAG, "Failed to get file name from URI", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (fileName == null)
|
||||
{
|
||||
// Try to get from path
|
||||
String path = uri.getPath();
|
||||
if (path != null)
|
||||
{
|
||||
int lastSlash = path.lastIndexOf('/');
|
||||
fileName = lastSlash >= 0 ? path.substring(lastSlash + 1) : path;
|
||||
}
|
||||
}
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all custom MWM files, grouped by map name with the newest version taking precedence.
|
||||
* @return Map from country name to CustomMwmFile (newest version)
|
||||
*/
|
||||
@NonNull
|
||||
public static Map<String, CustomMwmFile> getCustomMwmFiles(@NonNull Context context)
|
||||
{
|
||||
Map<String, CustomMwmFile> result = new HashMap<>();
|
||||
String customMapsDir = getCustomMapsDir(context);
|
||||
File rootDir = new File(customMapsDir);
|
||||
|
||||
if (!rootDir.exists() || !rootDir.isDirectory())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
File[] versionDirs = rootDir.listFiles(File::isDirectory);
|
||||
if (versionDirs == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Sort by version descending (newest first)
|
||||
Arrays.sort(versionDirs, (a, b) -> {
|
||||
long versionA = parseVersion(a.getName());
|
||||
long versionB = parseVersion(b.getName());
|
||||
return Long.compare(versionB, versionA);
|
||||
});
|
||||
|
||||
for (File versionDir : versionDirs)
|
||||
{
|
||||
long version = parseVersion(versionDir.getName());
|
||||
if (version <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
File[] mwmFiles = versionDir.listFiles((dir, name) ->
|
||||
name.toLowerCase(Locale.US).endsWith(MWM_EXTENSION));
|
||||
|
||||
if (mwmFiles == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (File mwmFile : mwmFiles)
|
||||
{
|
||||
String name = mwmFile.getName();
|
||||
// Remove .mwm extension to get country name
|
||||
String countryName = name.substring(0, name.length() - MWM_EXTENSION.length());
|
||||
|
||||
// Only add if we don't already have a newer version
|
||||
if (!result.containsKey(countryName))
|
||||
{
|
||||
result.put(countryName, new CustomMwmFile(
|
||||
countryName,
|
||||
mwmFile.getAbsolutePath(),
|
||||
version,
|
||||
mwmFile.length()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all version directories in the custom maps folder, sorted by version descending.
|
||||
* @return List of version directory paths
|
||||
*/
|
||||
@NonNull
|
||||
public static List<String> getCustomMapVersionDirs(@NonNull Context context)
|
||||
{
|
||||
List<String> result = new ArrayList<>();
|
||||
String customMapsDir = getCustomMapsDir(context);
|
||||
File rootDir = new File(customMapsDir);
|
||||
|
||||
if (!rootDir.exists() || !rootDir.isDirectory())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
File[] versionDirs = rootDir.listFiles(File::isDirectory);
|
||||
if (versionDirs == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Sort by version descending (newest first)
|
||||
Arrays.sort(versionDirs, (a, b) -> {
|
||||
long versionA = parseVersion(a.getName());
|
||||
long versionB = parseVersion(b.getName());
|
||||
return Long.compare(versionB, versionA);
|
||||
});
|
||||
|
||||
for (File versionDir : versionDirs)
|
||||
{
|
||||
if (parseVersion(versionDir.getName()) > 0)
|
||||
{
|
||||
result.add(StorageUtils.addTrailingSeparator(versionDir.getAbsolutePath()));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a custom version of a map exists.
|
||||
* @param countryName The country/map name (without .mwm extension)
|
||||
* @return The CustomMwmFile if found, null otherwise
|
||||
*/
|
||||
@Nullable
|
||||
public static CustomMwmFile getCustomMwmFile(@NonNull Context context, @NonNull String countryName)
|
||||
{
|
||||
Map<String, CustomMwmFile> customFiles = getCustomMwmFiles(context);
|
||||
return customFiles.get(countryName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a custom MWM file.
|
||||
* @param file The CustomMwmFile to delete
|
||||
* @return true if deletion was successful
|
||||
*/
|
||||
public static boolean deleteCustomMwmFile(@NonNull CustomMwmFile file)
|
||||
{
|
||||
File f = new File(file.path);
|
||||
boolean deleted = f.delete();
|
||||
|
||||
if (deleted)
|
||||
{
|
||||
Logger.i(TAG, "Deleted custom MWM file: " + file.path);
|
||||
|
||||
// Clean up empty version directories
|
||||
File parentDir = f.getParentFile();
|
||||
if (parentDir != null)
|
||||
{
|
||||
String[] remaining = parentDir.list();
|
||||
if (remaining != null && remaining.length == 0)
|
||||
{
|
||||
if (parentDir.delete())
|
||||
{
|
||||
Logger.i(TAG, "Deleted empty version directory: " + parentDir.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.e(TAG, "Failed to delete custom MWM file: " + file.path);
|
||||
}
|
||||
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a version string (YYMMDD format) to a long.
|
||||
* @return The version number, or 0 if parsing failed
|
||||
*/
|
||||
private static long parseVersion(String versionStr)
|
||||
{
|
||||
if (versionStr == null || versionStr.length() != 6)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Long.parseLong(versionStr);
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1
data/styles/default/dark/symbols/tree-special-m.svg
Normal file
1
data/styles/default/dark/symbols/tree-special-m.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="15" viewBox="0 0 15 15" width="15" xmlns="http://www.w3.org/2000/svg"><g fill="none" transform="translate(.0562 .4152)"><path d="m7.76536687 1.02582924 2.91781373 1.20859801c.4900562.20298792.8794043.59233606 1.0823922 1.08239221l1.208598 2.91781372c.2029879.49005615.2029879 1.04067751 0 1.5307337l-1.208598 2.91781362c-.2029879.4900562-.5923361.8794044-1.0823922 1.0823923l-2.91781376 1.208598c-.49005615.2029879-1.04067751.2029879-1.53073369 0l-2.91781366-1.208598c-.49005618-.2029879-.87940432-.592336-1.08239223-1.0823922l-1.20859802-2.91781373c-.2029879-.49005617-.2029879-1.04067755.00000001-1.53073373l1.208598-2.91781368c.20298791-.49005618.59233603-.8794043 1.0823922-1.0823922l2.9178137-1.20859802c.49005617-.2029879 1.04067755-.2029879 1.53073372 0z" fill="#181715"/><path d="m7.57402516 1.48776901 2.91781374 1.20859801c.3675421.15224093.6595532.44425202.8117941.81179415l1.208598 2.91781369c.1522409.36754213.1522409.78050817 0 1.1480503l-1.208598 2.91781374c-.1522409.3675421-.444252.6595532-.8117941.8117941l-2.91781374 1.208598c-.36754213.1522409-.78050817.1522409-1.1480503 0l-2.91781369-1.208598c-.36754213-.1522409-.65955322-.444252-.81179415-.8117941l-1.20859801-2.91781374c-.15224093-.36754213-.15224093-.78050817 0-1.1480503l1.20859801-2.91781369c.15224093-.36754213.44425202-.65955322.81179415-.81179415l2.91781369-1.20859801c.36754213-.15224093.78050817-.15224093 1.1480503 0z" fill="202510" fill-rule="evenodd"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -70,6 +70,7 @@ node|z14-[natural=geyser],
|
||||
node|z16-[natural=beach],
|
||||
area|z14-[natural=bare_rock],
|
||||
node|z17-[natural=rock],
|
||||
node|z17-[natural=tree],
|
||||
{text: name;text-color: @poi_label;text-position: center;text-offset: 1;}
|
||||
|
||||
node|z13-[natural=peak][!name],
|
||||
@@ -206,7 +207,9 @@ node|z19-[man_made=water_well][drinking_water=not],
|
||||
node|z19-[amenity=water_point][drinking_water=not],
|
||||
{icon-image: drinking-water-no-m.svg;}
|
||||
|
||||
node|z18-[natural=tree],
|
||||
node|z17-[natural=tree][name],
|
||||
{icon-image: tree-special-m.svg;}
|
||||
node|z18-[natural=tree][!name],
|
||||
{icon-image: tree-m.svg;}
|
||||
|
||||
node|z18-[xmas:feature=tree],
|
||||
|
||||
@@ -2460,7 +2460,7 @@ emergency-life_ring # icon z19- (also has captio
|
||||
power-substation # icon z17- (also has caption(optional) z18-, area z13-)
|
||||
=== -9990
|
||||
|
||||
natural-tree # icon z18-
|
||||
natural-tree # icon z17- (also has caption(optional) z17-)
|
||||
=== -9991
|
||||
|
||||
# amenity-bench # caption(optional) z19- (also has icon z18-)
|
||||
@@ -2499,6 +2499,7 @@ entrance-service # icon z19- (also has captio
|
||||
# man_made-survey_point # caption(optional) z18- (also has icon z18-)
|
||||
# man_made-telescope # caption(optional) z18- (also has icon z18-)
|
||||
# man_made-telescope-optical # caption(optional) z17- (also has icon z17-)
|
||||
# natural-tree # caption(optional) z17- (also has icon z17-)
|
||||
# power-substation # caption(optional) z18- (also has icon z17-, area z13-)
|
||||
# tourism-information # caption(optional) z16- (also has icon z16-)
|
||||
# tourism-information-board # caption(optional) z16- (also has icon z16-)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="18" height="18" version="1.1" viewBox="0 0 24 24" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="12" fill="#fff" opacity=".6"/><circle cx="12" cy="12" r="11" fill="#3b87c9"/><path d="m12 19.141c-2.6667 0-6-1.8076-6-6.141v-7.0295h4v6.9295c0 0.66086 0.66667 1.9 2 1.9 1.3333 0 2-1.0119 2-1.8v-7.0295h4v6.8295c0 4.5333-3.3333 6.341-6 6.341z" fill="#fff"/></svg>
|
||||
<svg width="18" height="18" version="1.1" viewBox="0 0 24 24" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="12" fill="#fff" opacity=".6"/><circle cx="12" cy="12" r="11" fill="#1267CE"/><path d="m12 19.141c-2.6667 0-6-1.8076-6-6.141v-7.0295h4v6.9295c0 0.66086 0.66667 1.9 2 1.9 1.3333 0 2-1.0119 2-1.8v-7.0295h4v6.8295c0 4.5333-3.3333 6.341-6 6.341z" fill="#fff"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 446 B After Width: | Height: | Size: 446 B |
@@ -2,7 +2,7 @@
|
||||
<svg width="14" height="14" version="1.1" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke-width=".75">
|
||||
<circle cx="9" cy="9" r="9" fill="#fff" opacity=".6"/>
|
||||
<circle cx="9" cy="9" r="8.25" fill="#3b87c9"/>
|
||||
<circle cx="9" cy="9" r="8.25" fill="#1267CE"/>
|
||||
<path d="m9 14.356c-2 0-4.5-1.3557-4.5-4.6057v-5.2722h3v5.1972c0 0.49565 0.5 1.425 1.5 1.425 1 0 1.5-0.75896 1.5-1.35v-5.2722h3v5.1222c0 3.4-2.5 4.7557-4.5 4.7557z" fill="#fff"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 462 B |
1
data/styles/default/light/symbols/tree-special-m.svg
Normal file
1
data/styles/default/light/symbols/tree-special-m.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="15" viewBox="0 0 15 15" width="15" xmlns="http://www.w3.org/2000/svg"><g fill="none" transform="translate(.0562 .4152)"><path d="m7.76536687 1.02582924 2.91781373 1.20859801c.4900562.20298792.8794043.59233606 1.0823922 1.08239221l1.208598 2.91781372c.2029879.49005615.2029879 1.04067751 0 1.5307337l-1.208598 2.91781362c-.2029879.4900562-.5923361.8794044-1.0823922 1.0823923l-2.91781376 1.208598c-.49005615.2029879-1.04067751.2029879-1.53073369 0l-2.91781366-1.208598c-.49005618-.2029879-.87940432-.592336-1.08239223-1.0823922l-1.20859802-2.91781373c-.2029879-.49005617-.2029879-1.04067755.00000001-1.53073373l1.208598-2.91781368c.20298791-.49005618.59233603-.8794043 1.0823922-1.0823922l2.9178137-1.20859802c.49005617-.2029879 1.04067755-.2029879 1.53073372 0z" fill="#f5eada"/><path d="m7.57402516 1.48776901 2.91781374 1.20859801c.3675421.15224093.6595532.44425202.8117941.81179415l1.208598 2.91781369c.1522409.36754213.1522409.78050817 0 1.1480503l-1.208598 2.91781374c-.1522409.3675421-.444252.6595532-.8117941.8117941l-2.91781374 1.208598c-.36754213.1522409-.78050817.1522409-1.1480503 0l-2.91781369-1.208598c-.36754213-.1522409-.65955322-.444252-.81179415-.8117941l-1.20859801-2.91781374c-.15224093-.36754213-.15224093-.78050817 0-1.1480503l1.20859801-2.91781369c.15224093-.36754213.44425202-.65955322.81179415-.81179415l2.91781369-1.20859801c.36754213-.15224093.78050817-.15224093 1.1480503 0z" fill="#a2ba4e" fill-rule="evenodd"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -31,6 +31,7 @@ node|z15-[natural=hot_spring],
|
||||
node|z12-[natural=geyser],
|
||||
node|z14-[natural=beach],
|
||||
area|z13-[natural=bare_rock][name],
|
||||
node|z16-[natural=tree],
|
||||
{text: name;text-color: @poi_label;text-position: center;text-offset: 1;font-size: 10;}
|
||||
node|z11-[natural=peak][!name],
|
||||
area|z13-[natural=bare_rock][!name],
|
||||
@@ -148,6 +149,11 @@ node|z15-[man_made=water_well][drinking_water=not],
|
||||
node|z15-[amenity=water_point][drinking_water=not],
|
||||
{icon-image: drinking-water-no-m.svg;}
|
||||
|
||||
node|z16-[natural=tree][name],
|
||||
{icon-image: tree-special-m.svg;}
|
||||
node|z18-[natural=tree][!name],
|
||||
{icon-image: tree-m.svg;}
|
||||
|
||||
|
||||
/* 3.5 Ferry terminal & seamarks */
|
||||
|
||||
|
||||
@@ -2466,7 +2466,7 @@ emergency-life_ring # icon z19- (also has captio
|
||||
power-substation # icon z17- (also has caption(optional) z18-, area z13-)
|
||||
=== -9990
|
||||
|
||||
natural-tree # icon z18-
|
||||
natural-tree # icon z16- (also has caption(optional) z16-)
|
||||
=== -9991
|
||||
|
||||
# amenity-bench # caption(optional) z19- (also has icon z18-)
|
||||
@@ -2505,6 +2505,7 @@ entrance-service # icon z19- (also has captio
|
||||
# man_made-survey_point # caption(optional) z15- (also has icon z14-)
|
||||
# man_made-telescope # caption(optional) z18- (also has icon z18-)
|
||||
# man_made-telescope-optical # caption(optional) z17- (also has icon z17-)
|
||||
# natural-tree # caption(optional) z16- (also has icon z16-)
|
||||
# power-substation # caption(optional) z18- (also has icon z17-, area z13-)
|
||||
# tourism-information # caption(optional) z15- (also has icon z15-)
|
||||
# tourism-information-board # caption(optional) z15- (also has icon z15-)
|
||||
|
||||
@@ -4,7 +4,6 @@ Thank you for your interest in contributing to CoMaps!
|
||||
|
||||
## How Can I Contribute?
|
||||
|
||||
- [Donate](https://opencollective.com/comaps/donate)
|
||||
- [Submit a bug report or a feature request](#bug-reports-and-feature-requests)
|
||||
|
||||
There are things to do for everyone:
|
||||
@@ -16,6 +15,8 @@ There are things to do for everyone:
|
||||
|
||||
If you'd like to help in any other way or if there are any related questions - please [contact us](https://codeberg.org/comaps#keep-connected).
|
||||
|
||||
If none of those ways of contributing seem to be a good fit, and you'd like to help out in another way, you could also [donate](https://www.comaps.app/donate/)
|
||||
|
||||
### Bug Reports and Feature Requests
|
||||
|
||||
[Submit an issue](https://codeberg.org/comaps/comaps/issues) and describe your feature idea or report a bug.
|
||||
@@ -47,13 +48,11 @@ Check the [map styling instructions](STYLES.md) and work on adding new map featu
|
||||
Please follow instructions in [INSTALL.md](INSTALL.md) to set up your development environment.
|
||||
You will find a list of issues for new contributors [here](https://codeberg.org/comaps/comaps/issues?labels=393881) to help you get started with simple tasks.
|
||||
|
||||
**We do not assign issues to first-time contributors.** Any such request notifies our contributors and the development team, and creates unnecessary noise that distracts us from the work. Just make a PR - and it will be reviewed.
|
||||
|
||||
Sometimes it's better to discuss and confirm your vision of the fix or implementation before working on an issue. Our main focus is on simplicity and convenience for everyone, not only for geeks.
|
||||
|
||||
Please [learn how to use `git rebase`](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) (or rebase via any git tool with a graphical interface, e.g. [Fork for Mac](https://git-fork.com/) is quite good) to amend your commits in the PR and maintain a clean logical commit history for your changes/branches.
|
||||
|
||||
We strive to help onboard new developers but we don't always have enough time to guide newcomers step-by-step and explain everything in detail. For that reason we might ask you to read lots of the documentation and study the existing code.
|
||||
We strive to help onboard new developers, but we don't always have enough time to guide newcomers step-by-step and explain everything in detail. So please make sure your changes follow these different guidelines when submitting a pull request:
|
||||
|
||||
- [Pull Request Guide](PR_GUIDE.md).
|
||||
- [Directories structure](STRUCTURE.md)
|
||||
@@ -71,7 +70,6 @@ Please help us:
|
||||
- reproducing and triaging reported bugs
|
||||
- [testing upcoming features and bug fixes for Android, iOS and desktop versions](TESTING.md)
|
||||
- keeping [issues](https://codeberg.org/comaps/comaps/issues) in order (check for duplicates, organize, assign labels, link related issues, etc.)
|
||||
- composing nice user-centric release notes and news items
|
||||
- etc.
|
||||
|
||||
You can also contribute in [other ways](https://codeberg.org/comaps/Governance/src/branch/main/contribute.md).
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# Roadmap
|
||||
|
||||
## Milestones
|
||||
|
||||
Milestones define when an issue, pull request, and/or roadmap item is to be completed. Issues are the what, milestones are the when. Development is complex therefore roadmap items can move between milestones depending on the remaining development and testing required to release a change.
|
||||
[View active milestones](https://codeberg.org/comaps/comaps/milestones).
|
||||
We do not have a formal roadmap, but we are working on a mission document to help understand our vision of the project. Also, we have an [App Priorities project](https://codeberg.org/comaps/comaps/projects/16883) on Codeberg to follow most important and asked features or changes.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "indexer/feature_data.hpp"
|
||||
#include "indexer/feature_visibility.hpp"
|
||||
#include "indexer/ftypes_matcher.hpp"
|
||||
#include "indexer/ftypes_subtypes.hpp"
|
||||
#include "indexer/scales.hpp"
|
||||
|
||||
#include "platform/distance.hpp"
|
||||
@@ -440,6 +441,17 @@ vector<int8_t> GetDescriptionLangPriority(RegionData const & regionData)
|
||||
return PrioritizedLanguages(preferredLangs, DefaultLanguage(regionData, preferredLangs));
|
||||
}
|
||||
|
||||
vector<string> GetLocalizedSubtypes(TypesHolder const & types)
|
||||
{
|
||||
auto const & classificator = classif();
|
||||
auto subtypes = ftypes::Subtypes::Instance();
|
||||
vector<string> localizedSubtypes;
|
||||
for (auto const & type : types)
|
||||
if (subtypes.IsSubtype(type))
|
||||
localizedSubtypes.push_back(platform::GetLocalizedTypeName(classificator.GetReadableObjectName(type)));
|
||||
return localizedSubtypes;
|
||||
}
|
||||
|
||||
vector<string> GetCuisines(TypesHolder const & types)
|
||||
{
|
||||
auto const & isCuisine = ftypes::IsCuisineChecker::Instance();
|
||||
|
||||
@@ -149,6 +149,9 @@ bool GetPreferredName(StringUtf8Multilang const & src, int8_t deviceLang, std::s
|
||||
/// - default language code;
|
||||
std::vector<int8_t> GetDescriptionLangPriority(RegionData const & regionData);
|
||||
|
||||
// Returns vector of subtypes localized by platform.
|
||||
std::vector<std::string> GetLocalizedSubtypes(TypesHolder const & types);
|
||||
|
||||
// Returns vector of cuisines readable names from classificator.
|
||||
std::vector<std::string> GetCuisines(TypesHolder const & types);
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ namespace
|
||||
char constexpr kBitsExt[] = ".bftsegbits";
|
||||
char constexpr kNodesExt[] = ".bftsegnodes";
|
||||
char constexpr kOffsetsExt[] = ".offsets";
|
||||
char constexpr kCustomMapsDir[] = "custom_maps";
|
||||
|
||||
string GetAdditionalWorldScope()
|
||||
{
|
||||
@@ -206,44 +205,6 @@ void FindAllDiffs(std::string const & dataDir, std::vector<LocalCountryFile> & d
|
||||
FindAllDiffsInDirectory(base::JoinPath(dir, fwt.first /* subdir */), diffs);
|
||||
}
|
||||
|
||||
void FindAllCustomMaps(string const & dataDir, std::vector<LocalCountryFile> & localFiles)
|
||||
{
|
||||
string const customMapsPath = base::JoinPath(GetDataDirFullPath(dataDir), kCustomMapsDir);
|
||||
|
||||
if (!Platform::IsFileExistsByFullPath(customMapsPath) || !Platform::IsDirectory(customMapsPath))
|
||||
return;
|
||||
|
||||
Platform::TFilesWithType versionDirs;
|
||||
Platform::GetFilesByType(customMapsPath, Platform::EFileType::Directory, versionDirs);
|
||||
|
||||
for (auto const & versionDir : versionDirs)
|
||||
{
|
||||
string const & subdir = versionDir.first;
|
||||
int64_t version;
|
||||
// Custom maps use YYMMDD format for version directories
|
||||
if (!ParseVersion(subdir, version))
|
||||
continue;
|
||||
|
||||
string const versionPath = base::JoinPath(customMapsPath, subdir);
|
||||
|
||||
Platform::TFilesWithType files;
|
||||
Platform::GetFilesByType(versionPath, Platform::EFileType::Regular, files);
|
||||
|
||||
for (auto const & file : files)
|
||||
{
|
||||
string name = file.first;
|
||||
if (!name.ends_with(DATA_FILE_EXTENSION))
|
||||
continue;
|
||||
|
||||
// Remove DATA_FILE_EXTENSION and use base name as a country file name.
|
||||
base::GetNameWithoutExt(name);
|
||||
localFiles.emplace_back(versionPath, CountryFile(std::move(name)), version);
|
||||
}
|
||||
}
|
||||
|
||||
LOG(LINFO, ("Found", localFiles.size(), "custom maps in", customMapsPath));
|
||||
}
|
||||
|
||||
void FindAllLocalMapsAndCleanup(int64_t latestVersion, std::vector<LocalCountryFile> & localFiles)
|
||||
{
|
||||
FindAllLocalMapsAndCleanup(latestVersion, string(), localFiles);
|
||||
@@ -262,10 +223,6 @@ void FindAllLocalMapsAndCleanup(int64_t latestVersion, string const & dataDir,
|
||||
for (auto const & fwt : fwts)
|
||||
{
|
||||
string const & subdir = fwt.first;
|
||||
// Skip the custom_maps directory - it's processed separately
|
||||
if (subdir == kCustomMapsDir)
|
||||
continue;
|
||||
|
||||
int64_t version;
|
||||
if (!ParseVersion(subdir, version) || version > latestVersion)
|
||||
continue;
|
||||
@@ -279,11 +236,6 @@ void FindAllLocalMapsAndCleanup(int64_t latestVersion, string const & dataDir,
|
||||
}
|
||||
}
|
||||
|
||||
// Find custom maps in the custom_maps directory.
|
||||
// Custom maps have higher priority and use future date versions (YYMMDD format).
|
||||
// The storage will prefer the newest version when multiple versions exist.
|
||||
FindAllCustomMaps(dataDir, localFiles);
|
||||
|
||||
// Check for World and WorldCoasts in app bundle or in resources.
|
||||
Platform & platform = GetPlatform();
|
||||
string const world(WORLD_FILE_NAME);
|
||||
|
||||
@@ -46,10 +46,6 @@ void FindAllLocalMapsAndCleanup(int64_t latestVersion, std::string const & dataD
|
||||
|
||||
void FindAllDiffs(std::string const & dataDir, std::vector<LocalCountryFile> & diffs);
|
||||
|
||||
// Finds custom MWM files in the custom_maps/YYMMDD/ directories.
|
||||
// Custom maps override downloaded maps when they have a newer version date.
|
||||
void FindAllCustomMaps(std::string const & dataDir, std::vector<LocalCountryFile> & localFiles);
|
||||
|
||||
// This method removes:
|
||||
// * partially downloaded non-latest maps (with version less than |latestVersion|)
|
||||
// * empty directories
|
||||
|
||||
@@ -279,7 +279,8 @@ void FillDetails(FeatureType & ft, std::string const & name, Result::Details & d
|
||||
}
|
||||
}
|
||||
|
||||
feature::TypesHolder const typesHolder(ft);
|
||||
feature::TypesHolder typesHolder(ft);
|
||||
typesHolder.SortBySpec();
|
||||
|
||||
std::string stars;
|
||||
uint8_t starsCount = 0;
|
||||
@@ -287,6 +288,8 @@ void FillDetails(FeatureType & ft, std::string const & name, Result::Details & d
|
||||
if (isHotel && strings::to_uint(ft.GetMetadata(feature::Metadata::FMD_STARS), starsCount))
|
||||
stars = feature::FormatStars(starsCount);
|
||||
|
||||
auto const subtypes = strings::JoinStrings(feature::GetLocalizedSubtypes(typesHolder), feature::kFieldsSeparator);
|
||||
|
||||
auto const cuisines = feature::GetLocalizedCuisines(typesHolder);
|
||||
auto const cuisine = strings::JoinStrings(cuisines, feature::kFieldsSeparator);
|
||||
|
||||
@@ -314,6 +317,7 @@ void FillDetails(FeatureType & ft, std::string const & name, Result::Details & d
|
||||
append(brand);
|
||||
append(elevation);
|
||||
append(cuisine);
|
||||
append(subtypes);
|
||||
append(fee);
|
||||
|
||||
details.m_description = std::move(description);
|
||||
|
||||
@@ -1666,9 +1666,6 @@ void Storage::GetNodeAttrs(CountryId const & countryId, NodeAttrs & nodeAttrs) c
|
||||
|
||||
nodeAttrs.m_localMwmCounter += 1;
|
||||
nodeAttrs.m_localMwmSize += localFile->GetSize(MapFileType::Map);
|
||||
// For leaf nodes, store the local file version
|
||||
if (d.ChildrenCount() == 0)
|
||||
nodeAttrs.m_localMwmVersion = localFile->GetVersion();
|
||||
});
|
||||
nodeAttrs.m_present = m_localFiles.find(countryId) != m_localFiles.end();
|
||||
|
||||
|
||||
@@ -54,7 +54,6 @@ struct NodeAttrs
|
||||
, m_mwmSize(0)
|
||||
, m_localMwmSize(0)
|
||||
, m_downloadingMwmSize(0)
|
||||
, m_localMwmVersion(0)
|
||||
, m_status(NodeStatus::Undefined)
|
||||
, m_error(NodeErrorCode::NoError)
|
||||
, m_present(false)
|
||||
@@ -89,10 +88,6 @@ struct NodeAttrs
|
||||
/// \note The size of leaves is the size is written in countries.txt.
|
||||
MwmSize m_downloadingMwmSize;
|
||||
|
||||
/// Version of the local mwm file (YYMMDD format, e.g. 251231).
|
||||
/// 0 if not downloaded.
|
||||
int64_t m_localMwmVersion;
|
||||
|
||||
/// The name of the node in a local language. That means the language dependent on
|
||||
/// a device locale.
|
||||
std::string m_nodeLocalName;
|
||||
|
||||
Reference in New Issue
Block a user