Compare commits

..

10 Commits

Author SHA1 Message Date
zyphlar
0941c7b2bd turn off exceptions and notifications for diff
Signed-off-by: zyphlar <zyphlar@noreply.codeberg.org>
2026-01-21 18:36:36 +01:00
zyphlar
e714138ce7 [ci] fix subway cicd (#3011)
Signed-off-by: zyphlar <zyphlar@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/comaps/comaps/pulls/3011
Reviewed-by: Konstantin Pastbin <pastk@noreply.codeberg.org>
2026-01-21 17:12:48 +01:00
Yannik Bloscheck
9969508193 [styles] Display names for trees and make those slightly better visibible in forests
Signed-off-by: Yannik Bloscheck <git@yannikbloscheck.com>
2026-01-21 14:10:47 +01:00
Yannik Bloscheck
08daf740e2 Display subtypes in search results
Signed-off-by: Yannik Bloscheck <git@yannikbloscheck.com>
2026-01-21 14:10:22 +01:00
vikiawv
c34c109abf Minor fix to Subway Vienna Color after some further investigation
Signed-off-by: vikiawv <vikiawv@noreply.codeberg.org>
2026-01-20 22:01:23 +01:00
vikiawv
e4c2961a5e Minor fix to Subway Vienna Color after some further investigation
Signed-off-by: vikiawv <vikiawv@noreply.codeberg.org>
2026-01-20 22:01:23 +01:00
Jean-Baptiste
e156d21eee [doc] Improve some docs
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2026-01-20 18:13:59 +01:00
Harry Bond
7a3e3159c0 [Android] Fix small bump on track recording icon
extremely minor, but couldn't unsee it!

Signed-off-by: Harry Bond <me@hbond.xyz>
2026-01-20 12:25:55 +00:00
Harry Bond
64f3defc6d [android] slightly adjust zoom button spacing
results in the in-between spacing being the same height as the rounding, looks a tad more consistent

Signed-off-by: Harry Bond <me@hbond.xyz>
2026-01-20 06:45:17 +01:00
Jean-Baptiste
b59b3f610f [android] Improve color of track recording icons
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2026-01-19 18:44:08 +01:00
30 changed files with 165 additions and 658 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -331,7 +331,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
}
final IntentProcessor[] mIntentProcessors = {
new Factory.MwmFileProcessor(),
new Factory.UrlProcessor(),
new Factory.KmzKmlProcessor(),
};

View File

@@ -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>

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);

View File

@@ -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) + "% }";
}
}

View File

@@ -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;
}
}
}

View 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

View File

@@ -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],

View File

@@ -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-)

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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 */

View File

@@ -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-)

View File

@@ -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).

View File

@@ -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.

View File

@@ -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();

View File

@@ -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);

View File

@@ -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);

View File

@@ -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

View File

@@ -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);

View File

@@ -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();

View File

@@ -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;