forked from MapComplete/MapComplete
		
	Favourites: improve overview, update all features from OSM when loading
This commit is contained in:
		
							parent
							
								
									35f8b9d8f2
								
							
						
					
					
						commit
						56a23deb2d
					
				
					 10 changed files with 231 additions and 234 deletions
				
			
		|  | @ -501,147 +501,43 @@ export class GeoOperations { | |||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     public static IdentifieCommonSegments(coordinatess: [number, number][][]): { | ||||
|         originalIndex: number | ||||
|         segmentShardWith: number[] | ||||
|         coordinates: [] | ||||
|     }[] { | ||||
|         // An edge. Note that the edge might be reversed to fix the sorting condition:  start[0] < end[0] && (start[0] != end[0] || start[0] < end[1])
 | ||||
|         type edge = { | ||||
|             start: [number, number] | ||||
|             end: [number, number] | ||||
|             intermediate: [number, number][] | ||||
|             members: { index: number; isReversed: boolean }[] | ||||
|     /** | ||||
|      * Given a list of points, convert into a GPX-list, e.g. for favourites | ||||
|      * @param locations | ||||
|      * @param title | ||||
|      */ | ||||
|     public static toGpxPoints( | ||||
|         locations: Feature<Point, { date?: string; altitude?: number | string }>[], | ||||
|         title?: string | ||||
|     ) { | ||||
|         title = title?.trim() | ||||
|         if (title === undefined || title === "") { | ||||
|             title = "Created with MapComplete" | ||||
|         } | ||||
| 
 | ||||
|         // The strategy:
 | ||||
|         // 1. Index _all_ edges from _every_ linestring. Index them by starting key, gather which relations run over them
 | ||||
|         // 2. Join these edges back together - as long as their membership groups are the same
 | ||||
|         // 3. Convert to results
 | ||||
| 
 | ||||
|         const allEdgesByKey = new Map<string, edge>() | ||||
| 
 | ||||
|         for (let index = 0; index < coordinatess.length; index++) { | ||||
|             const coordinates = coordinatess[index] | ||||
|             for (let i = 0; i < coordinates.length - 1; i++) { | ||||
|                 const c0 = coordinates[i] | ||||
|                 const c1 = coordinates[i + 1] | ||||
|                 const isReversed = c0[0] > c1[0] || (c0[0] == c1[0] && c0[1] > c1[1]) | ||||
| 
 | ||||
|                 let key: string | ||||
|                 if (isReversed) { | ||||
|                     key = "" + c1 + ";" + c0 | ||||
|                 } else { | ||||
|                     key = "" + c0 + ";" + c1 | ||||
|         title = Utils.EncodeXmlValue(title) | ||||
|         const trackPoints: string[] = [] | ||||
|         for (const l of locations) { | ||||
|             let trkpt = `    <wpt lat="${l.geometry.coordinates[1]}" lon="${l.geometry.coordinates[0]}">` | ||||
|             for (const key in l.properties) { | ||||
|                 const keyCleaned = key.replaceAll(":", "__") | ||||
|                 trkpt += `        <${keyCleaned}>${l.properties[key]}</${keyCleaned}>\n` | ||||
|                 if (key === "website") { | ||||
|                     trkpt += `        <link>${l.properties[key]}</link>\n` | ||||
|                 } | ||||
|                 const member = { index, isReversed } | ||||
|                 if (allEdgesByKey.has(key)) { | ||||
|                     allEdgesByKey.get(key).members.push(member) | ||||
|                     continue | ||||
|                 } | ||||
| 
 | ||||
|                 let edge: edge | ||||
|                 if (!isReversed) { | ||||
|                     edge = { | ||||
|                         start: c0, | ||||
|                         end: c1, | ||||
|                         members: [member], | ||||
|                         intermediate: [], | ||||
|                     } | ||||
|                 } else { | ||||
|                     edge = { | ||||
|                         start: c1, | ||||
|                         end: c0, | ||||
|                         members: [member], | ||||
|                         intermediate: [], | ||||
|                     } | ||||
|                 } | ||||
|                 allEdgesByKey.set(key, edge) | ||||
|             } | ||||
|             trkpt += "    </wpt>\n" | ||||
|             trackPoints.push(trkpt) | ||||
|         } | ||||
| 
 | ||||
|         // Lets merge them back together!
 | ||||
| 
 | ||||
|         let didMergeSomething = false | ||||
|         let allMergedEdges = Array.from(allEdgesByKey.values()) | ||||
|         const allEdgesByStartPoint = new Map<string, edge[]>() | ||||
|         for (const edge of allMergedEdges) { | ||||
|             edge.members.sort((m0, m1) => m0.index - m1.index) | ||||
| 
 | ||||
|             const kstart = edge.start + "" | ||||
|             if (!allEdgesByStartPoint.has(kstart)) { | ||||
|                 allEdgesByStartPoint.set(kstart, []) | ||||
|             } | ||||
|             allEdgesByStartPoint.get(kstart).push(edge) | ||||
|         } | ||||
| 
 | ||||
|         function membersAreCompatible(first: edge, second: edge): boolean { | ||||
|             // There must be an exact match between the members
 | ||||
|             if (first.members === second.members) { | ||||
|                 return true | ||||
|             } | ||||
| 
 | ||||
|             if (first.members.length !== second.members.length) { | ||||
|                 return false | ||||
|             } | ||||
| 
 | ||||
|             // Members are sorted and have the same length, so we can check quickly
 | ||||
|             for (let i = 0; i < first.members.length; i++) { | ||||
|                 const m0 = first.members[i] | ||||
|                 const m1 = second.members[i] | ||||
|                 if (m0.index !== m1.index || m0.isReversed !== m1.isReversed) { | ||||
|                     return false | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // Allrigth, they are the same, lets mark this permanently
 | ||||
|             second.members = first.members | ||||
|             return true | ||||
|         } | ||||
| 
 | ||||
|         do { | ||||
|             didMergeSomething = false | ||||
|             // We use 'allMergedEdges' as our running list
 | ||||
|             const consumed = new Set<edge>() | ||||
|             for (const edge of allMergedEdges) { | ||||
|                 // Can we make this edge longer at the end?
 | ||||
|                 if (consumed.has(edge)) { | ||||
|                     continue | ||||
|                 } | ||||
| 
 | ||||
|                 console.log("Considering edge", edge) | ||||
|                 const matchingEndEdges = allEdgesByStartPoint.get(edge.end + "") | ||||
|                 console.log("Matchign endpoints:", matchingEndEdges) | ||||
|                 if (matchingEndEdges === undefined) { | ||||
|                     continue | ||||
|                 } | ||||
| 
 | ||||
|                 for (let i = 0; i < matchingEndEdges.length; i++) { | ||||
|                     const endEdge = matchingEndEdges[i] | ||||
| 
 | ||||
|                     if (consumed.has(endEdge)) { | ||||
|                         continue | ||||
|                     } | ||||
| 
 | ||||
|                     if (!membersAreCompatible(edge, endEdge)) { | ||||
|                         continue | ||||
|                     } | ||||
| 
 | ||||
|                     // We can make the segment longer!
 | ||||
|                     didMergeSomething = true | ||||
|                     console.log("Merging ", edge, "with ", endEdge) | ||||
|                     edge.intermediate.push(edge.end) | ||||
|                     edge.end = endEdge.end | ||||
|                     consumed.add(endEdge) | ||||
|                     matchingEndEdges.splice(i, 1) | ||||
|                     break | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             allMergedEdges = allMergedEdges.filter((edge) => !consumed.has(edge)) | ||||
|         } while (didMergeSomething) | ||||
| 
 | ||||
|         return [] | ||||
|         const header = | ||||
|             '<gpx version="1.1" creator="mapcomplete.org" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">' | ||||
|         return ( | ||||
|             header + | ||||
|             "\n<name>" + | ||||
|             title + | ||||
|             "</name>\n<trk><trkseg>\n" + | ||||
|             trackPoints.join("\n") + | ||||
|             "\n</trkseg></trk></gpx>" | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue