Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of ClickableWay for solitary ways (piste / dirtbike / mtb) #21714

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
933bcfc
Add skel classes for proto of ClickableWay feature
RZR-UA Jan 9, 2025
aee346d
Support ClickableWay for v1, polish isClickableWay
RZR-UA Jan 10, 2025
20ea52a
Split V1/V2 methods and implement calcSearchRadius
RZR-UA Jan 10, 2025
1ce52ec
Add skel of GpxFile with activity, name, tags
RZR-UA Jan 10, 2025
51db28b
Merge branch 'master' into rzr-select-way
RZR-UA Jan 10, 2025
2981f13
Implement public Algorithms.sanitizeFileName()
RZR-UA Jan 10, 2025
fb6e58c
Implement save/open GpxFile, refresh TODO list
RZR-UA Jan 10, 2025
e376691
Merge branch 'master' into rzr-select-way
RZR-UA Jan 13, 2025
4a301a7
Fix awkward "X (X)" when name == ref (rendering)
RZR-UA Jan 13, 2025
ef1731f
Implement gpxColors by difficulty/scale tags
RZR-UA Jan 13, 2025
9923629
Add isUniqueClickableWay(), refactor previous code
RZR-UA Jan 13, 2025
56a9dc2
Merge branch 'master' into rzr-select-way
RZR-UA Jan 14, 2025
9eefc8e
Draft ClickableWayReaderTask async task, refactor
RZR-UA Jan 14, 2025
c56c4a1
Merge branch 'master' into rzr-select-way
RZR-UA Jan 15, 2025
9d10303
Avoid useless error log
RZR-UA Jan 15, 2025
285209c
Simplify/refactor ClickableWay structure
RZR-UA Jan 15, 2025
338c133
Migrate to OsmAnd-shared in NetworkRouteSelector
RZR-UA Jan 15, 2025
c93de76
Refactor naming of ClickableWay classes
RZR-UA Jan 15, 2025
8a7cea9
Draft HeightDataLoader for ClickableWay (backend)
RZR-UA Jan 15, 2025
f7231de
Merge branch 'master' into rzr-select-way
RZR-UA Jan 16, 2025
51e0216
Merge branch 'master' into rzr-select-way
RZR-UA Jan 16, 2025
3f66cc9
Apply loadHeightDataAsWaypoints to clickableWay
RZR-UA Jan 16, 2025
15ac702
Fix potential NPE in readHeightData()
RZR-UA Jan 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions OsmAnd-java/src/main/java/net/osmand/binary/HeightDataLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package net.osmand.binary;

import static net.osmand.router.RouteResultPreparation.SHIFT_ID;

import net.osmand.PlatformUtil;
import net.osmand.ResultMatcher;
import net.osmand.binary.BinaryMapRouteReaderAdapter.RouteSubregion;
import net.osmand.data.QuadRect;
import net.osmand.shared.gpx.primitives.WptPt;
import net.osmand.util.MapUtils;

import org.apache.commons.logging.Log;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class HeightDataLoader {
public static final int ZOOM_TO_LOAD_TILES = 15;
public static final int ZOOM_TO_LOAD_TILES_SHIFT_L = ZOOM_TO_LOAD_TILES + 1;
public static final int ZOOM_TO_LOAD_TILES_SHIFT_R = 31 - ZOOM_TO_LOAD_TILES;

private final static Log log = PlatformUtil.getLog(HeightDataLoader.class);
private final Map<RouteSubregion, List<RouteDataObject>> loadedSubregions = new HashMap<>();
private final Map<BinaryMapIndexReader, List<RouteSubregion>> readers = new LinkedHashMap<>();

public HeightDataLoader(BinaryMapIndexReader[] readers) {
for (BinaryMapIndexReader r : readers) {
List<RouteSubregion> subregions = new ArrayList<>();
for (BinaryMapRouteReaderAdapter.RouteRegion rInd : r.getRoutingIndexes()) {
List<RouteSubregion> subregs = rInd.getSubregions();
// create a copy to avoid leaks to the original structure
for (RouteSubregion rs : subregs) {
subregions.add(new RouteSubregion(rs));
}
}
this.readers.put(r, subregions);
}
}

public List<WptPt> loadHeightDataAsWaypoints(long osmId, QuadRect bbox31) {
Map<Long, RouteDataObject> results = new HashMap<>();
ResultMatcher<RouteDataObject> matcher = new ResultMatcher<>() {
@Override
public boolean publish(RouteDataObject routeDataObject) {
return routeDataObject != null && routeDataObject.getId() >> SHIFT_ID == osmId;
}

@Override
public boolean isCancelled() {
return results.containsKey(osmId); // fast up search
}
};

try {
loadRouteDataObjects(bbox31, results, matcher);
} catch (IOException e) {
log.error(e);
}

RouteDataObject found = results.get(osmId);
if (found != null && found.getPointsLength() > 0) {
List<WptPt> waypoints = new ArrayList<>();
float[] heightArray = found.calculateHeightArray();
for (int i = 0; i < found.getPointsLength(); i++) {
WptPt point = new WptPt();
point.setLat(MapUtils.get31LatitudeY(found.getPoint31YTile(i)));
point.setLon(MapUtils.get31LongitudeX(found.getPoint31XTile(i)));
if (heightArray != null && heightArray.length > i * 2 + 1) {
point.setEle(heightArray[i * 2 + 1]);
}
waypoints.add(point);
}
return waypoints;
}

return null;
}

private boolean loadRouteDataObjects(QuadRect bbox31,
Map<Long, RouteDataObject> results,
ResultMatcher<RouteDataObject> matcher) throws IOException {
int loaded = 0;
int left = (int) bbox31.left >> ZOOM_TO_LOAD_TILES_SHIFT_R;
int top = (int) bbox31.top >> ZOOM_TO_LOAD_TILES_SHIFT_R;
int right = (int) bbox31.right >> ZOOM_TO_LOAD_TILES_SHIFT_R;
int bottom = (int) bbox31.bottom >> ZOOM_TO_LOAD_TILES_SHIFT_R;
for (int x = left; x <= right; x++) {
for (int y = top; y <= bottom; y++) {
if (matcher != null && matcher.isCancelled()) {
return loaded > 0;
}
loaded += loadRouteDataObjects(x, y, results, matcher);
}
}
return loaded > 0;
}

private int loadRouteDataObjects(int x, int y,
Map<Long, RouteDataObject> results,
ResultMatcher<RouteDataObject> matcher) throws IOException {
int loaded = 0;
HashSet<Long> deletedIds = new HashSet<>();
Map<Long, BinaryMapRouteReaderAdapter.RouteRegion> usedIds = new HashMap<>();
BinaryMapIndexReader.SearchRequest<RouteDataObject> req = BinaryMapIndexReader.buildSearchRouteRequest(
x << ZOOM_TO_LOAD_TILES_SHIFT_L, (x + 1) << ZOOM_TO_LOAD_TILES_SHIFT_L,
y << ZOOM_TO_LOAD_TILES_SHIFT_L, (y + 1) << ZOOM_TO_LOAD_TILES_SHIFT_L, null);
for (Map.Entry<BinaryMapIndexReader, List<RouteSubregion>> readerSubregions : readers.entrySet()) {
req.clearSearchResults();
BinaryMapIndexReader reader = readerSubregions.getKey();
synchronized (reader) {
List<RouteSubregion> routeSubregions = readerSubregions.getValue();
List<RouteSubregion> subregions = reader.searchRouteIndexTree(req, routeSubregions);
for (RouteSubregion sub : subregions) {
List<RouteDataObject> objects = loadedSubregions.get(sub);
if (objects == null) {
objects = reader.loadRouteIndexData(sub);
loadedSubregions.put(sub, objects);
}
for (RouteDataObject obj : objects) {
if (matcher != null && matcher.isCancelled()) {
return loaded;
}
if (matcher == null || matcher.publish(obj)) {
if (deletedIds.contains(obj.id)) {
// live-updates, osmand_change=delete
continue;
}
if (obj.isRoadDeleted()) {
deletedIds.add(obj.id);
continue;
}
if (usedIds.containsKey(obj.id) && usedIds.get(obj.id) != obj.region) {
// live-update, changed tags
continue;
}
loaded += (results.put(obj.getId() >> SHIFT_ID, obj) == null) ? 1 : 0;
usedIds.put(obj.id, obj.region);
}
}
}
}
}
return loaded;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
import net.osmand.binary.BinaryMapIndexReader;
import net.osmand.binary.RouteDataObject;
import net.osmand.data.QuadRect;
import net.osmand.gpx.GPXFile;
import net.osmand.gpx.GPXUtilities;
import net.osmand.osm.OsmRouteType;
import net.osmand.router.network.NetworkRouteContext.NetworkRouteSegment;
import net.osmand.shared.gpx.GpxFile;
import net.osmand.shared.gpx.primitives.Track;
import net.osmand.shared.gpx.primitives.TrkSegment;
import net.osmand.shared.gpx.primitives.WptPt;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
import net.osmand.util.TransliterationHelper;
Expand Down Expand Up @@ -81,20 +83,20 @@ public boolean isCancelled() {
return callback != null && callback.isCancelled();
}

public Map<RouteKey, GPXFile> getRoutes(RenderedObject renderedObject) throws IOException {
public Map<RouteKey, GpxFile> getRoutes(RenderedObject renderedObject) throws IOException {
int x = renderedObject.getX().get(0);
int y = renderedObject.getY().get(0);
return getRoutes(x, y, true);
}

public Map<RouteKey, GPXFile> getRoutes(RenderedObject renderedObject, boolean loadRoutes) throws IOException {
public Map<RouteKey, GpxFile> getRoutes(RenderedObject renderedObject, boolean loadRoutes) throws IOException {
int x = renderedObject.getX().get(0);
int y = renderedObject.getY().get(0);
return getRoutes(x, y, loadRoutes);
}

public Map<RouteKey, GPXFile> getRoutes(int x, int y, boolean loadRoutes) throws IOException {
Map<RouteKey, GPXFile> res = new LinkedHashMap<>();
public Map<RouteKey, GpxFile> getRoutes(int x, int y, boolean loadRoutes) throws IOException {
Map<RouteKey, GpxFile> res = new LinkedHashMap<>();
for (NetworkRouteSegment segment : rCtx.loadRouteSegment(x, y)) {
if (res.containsKey(segment.routeKey)) {
continue;
Expand All @@ -112,13 +114,13 @@ public Map<RouteKey, GPXFile> getRoutes(int x, int y, boolean loadRoutes) throws
return res;
}

public Map<RouteKey, GPXFile> getRoutes(QuadRect bBox, boolean loadRoutes, RouteKey selected) throws IOException {
public Map<RouteKey, GpxFile> getRoutes(QuadRect bBox, boolean loadRoutes, RouteKey selected) throws IOException {
int y31T = MapUtils.get31TileNumberY(Math.max(bBox.bottom, bBox.top));
int y31B = MapUtils.get31TileNumberY(Math.min(bBox.bottom, bBox.top));
int x31L = MapUtils.get31TileNumberX(bBox.left);
int x31R = MapUtils.get31TileNumberX(bBox.right);
Map<RouteKey, List<NetworkRouteSegment>> routeSegmentTile = rCtx.loadRouteSegmentsBbox(x31L, y31T, x31R, y31B, null);
Map<RouteKey, GPXFile> gpxFileMap = new LinkedHashMap<>();
Map<RouteKey, GpxFile> gpxFileMap = new LinkedHashMap<>();
for (RouteKey routeKey : routeSegmentTile.keySet()) {
if (selected != null && !selected.equals(routeKey)) {
continue;
Expand Down Expand Up @@ -224,7 +226,7 @@ private List<NetworkRouteSegmentChain> getByPoint(Map<Long, List<NetworkRouteSeg
return list;
}

private void connectAlgorithm(NetworkRouteSegment segment, Map<RouteKey, GPXFile> res) throws IOException {
private void connectAlgorithm(NetworkRouteSegment segment, Map<RouteKey, GpxFile> res) throws IOException {
RouteKey rkey = segment.routeKey;
List<NetworkRouteSegment> loaded = new ArrayList<>();
debug("START ", null, segment);
Expand All @@ -234,7 +236,7 @@ private void connectAlgorithm(NetworkRouteSegment segment, Map<RouteKey, GPXFile
}


List<NetworkRouteSegmentChain> getNetworkRouteSegmentChains(RouteKey routeKey, Map<RouteKey, GPXFile> res, List<NetworkRouteSegment> loaded) {
List<NetworkRouteSegmentChain> getNetworkRouteSegmentChains(RouteKey routeKey, Map<RouteKey, GpxFile> res, List<NetworkRouteSegment> loaded) {
System.out.println("About to merge: " + loaded.size());
Map<Long, List<NetworkRouteSegmentChain>> chains = createChainStructure(loaded);
Map<Long, List<NetworkRouteSegmentChain>> endChains = prepareEndChain(chains);
Expand All @@ -247,7 +249,7 @@ List<NetworkRouteSegmentChain> getNetworkRouteSegmentChains(RouteKey routeKey, M
connectSimpleMerge(chains, endChains, 0, CONNECT_POINTS_DISTANCE_STEP);
connectSimpleMerge(chains, endChains, CONNECT_POINTS_DISTANCE_MAX / 2, CONNECT_POINTS_DISTANCE_MAX);
List<NetworkRouteSegmentChain> lst = flattenChainStructure(chains);
GPXFile gpxFile = createGpxFile(lst, routeKey);
GpxFile gpxFile = createGpxFile(lst, routeKey);
res.put(routeKey, gpxFile);
return lst;
}
Expand Down Expand Up @@ -570,7 +572,7 @@ private void addEnclosedTiles(TLongArrayList queue, long tile) {
}
}

private void growAlgorithm(NetworkRouteSegment segment, Map<RouteKey, GPXFile> res) throws IOException {
private void growAlgorithm(NetworkRouteSegment segment, Map<RouteKey, GpxFile> res) throws IOException {
List<NetworkRouteSegment> lst = new ArrayList<>();
TLongHashSet visitedIds = new TLongHashSet();
visitedIds.add(segment.getId());
Expand Down Expand Up @@ -641,37 +643,37 @@ private boolean grow(List<NetworkRouteSegment> lst, TLongHashSet visitedIds, boo
return false;
}

private GPXFile createGpxFile(List<NetworkRouteSegmentChain> chains, RouteKey routeKey) {
GPXFile gpxFile = new GPXFile(null, null, null);
GPXUtilities.Track track = new GPXUtilities.Track();
GPXUtilities.TrkSegment trkSegment;
private GpxFile createGpxFile(List<NetworkRouteSegmentChain> chains, RouteKey routeKey) {
GpxFile gpxFile = new GpxFile(null, null, null);
Track track = new Track();
TrkSegment trkSegment;
List<Integer> sizes = new ArrayList<>();
for (NetworkRouteSegmentChain c : chains) {
List<NetworkRouteSegment> segmentList = new ArrayList<>();
segmentList.add(c.start);
if (c.connected != null) {
segmentList.addAll(c.connected);
}
trkSegment = new GPXUtilities.TrkSegment();
track.segments.add(trkSegment);
trkSegment = new TrkSegment();
track.getSegments().add(trkSegment);
int l = 0;
GPXUtilities.WptPt prev = null;
WptPt prev = null;
for (NetworkRouteSegment segment : segmentList) {
float[] heightArray = null;
if (segment.robj != null) {
heightArray = segment.robj.calculateHeightArray();
}
int inc = segment.start < segment.end ? 1 : -1;
for (int i = segment.start; ; i += inc) {
GPXUtilities.WptPt point = new GPXUtilities.WptPt();
point.lat = MapUtils.get31LatitudeY(segment.getPoint31YTile(i));
point.lon = MapUtils.get31LongitudeX(segment.getPoint31XTile(i));
WptPt point = new WptPt();
point.setLat(MapUtils.get31LatitudeY(segment.getPoint31YTile(i)));
point.setLon(MapUtils.get31LongitudeX(segment.getPoint31XTile(i)));
if (heightArray != null && heightArray.length > i * 2 + 1) {
point.ele = heightArray[i * 2 + 1];
point.setEle(heightArray[i * 2 + 1]);
}
trkSegment.points.add(point);
trkSegment.getPoints().add(point);
if (prev != null) {
l += MapUtils.getDistance(prev.lat, prev.lon, point.lat, point.lon);
l += MapUtils.getDistance(prev.getLat(), prev.getLon(), point.getLat(), point.getLon());
}
prev = point;
if (i == segment.end) {
Expand All @@ -681,8 +683,8 @@ private GPXFile createGpxFile(List<NetworkRouteSegmentChain> chains, RouteKey ro
}
sizes.add(l);
}
System.out.println(String.format("Segments size %d: %s", track.segments.size(), sizes.toString()));
gpxFile.tracks.add(track);
System.out.println(String.format("Segments size %d: %s", track.getSegments().size(), sizes.toString()));
gpxFile.getTracks().add(track);
gpxFile.addRouteKeyTags(routeKey.tagsToGpx());
return gpxFile;
}
Expand Down
21 changes: 21 additions & 0 deletions OsmAnd-java/src/main/java/net/osmand/util/Algorithms.java
Original file line number Diff line number Diff line change
Expand Up @@ -1365,4 +1365,25 @@ public static String splitAndClearRepeats(String ref, String symbol) {
return res;
}

public static String sanitizeFileName(String fileName) {
return fileName
.replace("/", "_")
.replace("\\", "_")
.replace(":", "_")
.replace(";", "_")
.replace("*", "_")
.replace("?", "_")
.replace("`", "_")
.replace("\'", "_")
.replace("\"", "_")
.replace("<", "_")
.replace(">", "_")
.replace("|", "_")
.replace("&", "_")
.replace("\0", "_")
.replace("\n", "_")
.replace("\r", "_")
.replace("\t", " ")
.trim();
}
}
4 changes: 4 additions & 0 deletions OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuController.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import net.osmand.plus.plugins.parking.ParkingPositionMenuController;
import net.osmand.plus.plugins.srtm.SRTMPlugin;
import net.osmand.plus.resources.SearchOsmandRegionTask;
import net.osmand.plus.track.clickable.ClickableWay;
import net.osmand.plus.track.helpers.GpxDisplayItem;
import net.osmand.plus.transport.TransportStopRoute;
import net.osmand.plus.utils.NativeUtilities;
Expand Down Expand Up @@ -219,6 +220,9 @@ public static MenuController getMenuController(@NonNull MapActivity mapActivity,
menuController = new MapillaryMenuController(mapActivity, pointDescription, (MapillaryImage) object);
} else if (object instanceof SelectedGpxPoint) {
menuController = new SelectedGpxMenuController(mapActivity, pointDescription, (SelectedGpxPoint) object);
} else if (object instanceof ClickableWay) {
SelectedGpxPoint point = ((ClickableWay) object).getSelectedGpxPoint();
menuController = new SelectedGpxMenuController(mapActivity, pointDescription, point);
} else if (object instanceof Pair) {
Pair<?, ?> pair = (Pair<?, ?>) object;
if (pair.second instanceof SelectedGpxPoint) {
Expand Down
2 changes: 1 addition & 1 deletion OsmAnd/src/net/osmand/plus/render/TextRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ private void createTextDrawInfo(BinaryMapDataObject o, RenderingRuleSearchReques
public boolean execute(int tagid, String nname) {
String tagNameN2 = o.getMapIndex().decodeType(tagid).tag;
if (tagName2.equals(tagNameN2)) {
if (nname != null && nname.trim().length() > 0) {
if (nname != null && nname.trim().length() > 0 && !nname.equals(text.text)) {
text.text += " (" + nname + ")";
}
return false;
Expand Down
6 changes: 4 additions & 2 deletions OsmAnd/src/net/osmand/plus/track/cards/DescriptionCard.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ public void updateContent() {
String imageUrl = getMetadataImageLink(gpxFile.getMetadata());
String descriptionHtml = gpxFile.getMetadata().getDescription();

AppCompatImageView imageView = view.findViewById(R.id.main_image);
PicassoUtils.setupImageViewByUrl(app, imageView, imageUrl, true);
if (PicassoUtils.isImageUrl(imageUrl)) {
AppCompatImageView imageView = view.findViewById(R.id.main_image);
PicassoUtils.setupImageViewByUrl(app, imageView, imageUrl, true);
}

if (Algorithms.isBlank(descriptionHtml)) {
showAddBtn();
Expand Down
Loading
Loading