我有一个MultiLineString,它由构成路径的各个LineStrings组成。路径具有方向,并且必须对LineStrings进行排序以反映此顺序。要做到这一点,必须颠倒一些字符串以指向与其余字符串相同的方向。 什么是合适的算法来完成这项任务?
换句话说,对列表列表进行排序的最佳方法是什么,列表可以反转?即
输入:
[2, 1] [4, 5] [0, 1] [5, 6] [9, 8]
输出:
[0, 1] [1, 2] [4, 5] [5, 6] [8, 9]
答案 0 :(得分:3)
Sorted()
<强>实施例强>
l = [[2, 1] ,[4, 5], [0, 1], [5, 6], [9, 8]]
print(sorted([sorted(i) for i in l]))
<强>输出:强>
[[0, 1], [1, 2], [4, 5], [5, 6], [8, 9]]
答案 1 :(得分:0)
未能找到解决方案,我最终编写了这个算法。它完成了这项工作,但可以更好地处理分支,即。选择会产生最长连续路径的分支。现在它只是坚持第一线段并从那里继续。
给定GeoJSON MultiLineString几何体,算法将线段排序为连续路径并返回新几何体。
该代码根据您想要公共许可证做什么来获得许可。
import math
from collections import namedtuple
from operator import attrgetter
from copy import deepcopy
def arrange_geometry(original_geometry):
def distance(coords1, coords2):
return math.sqrt(math.pow(coords1[0] - coords2[0], 2) + math.pow(coords1[1] - coords2[1], 2))
MinDistance = namedtuple('MinDistance', 'target distance offset reverse_target')
geometry = deepcopy(original_geometry)
if geometry['type'] == 'MultiLineString':
lines = geometry['coordinates']
sorted_multistring = [lines.pop(0)]
while lines:
min_distances = []
for line in lines:
source_a = sorted_multistring[0][0]
source_b = sorted_multistring[-1][-1]
target_a = line[0]
target_b = line[-1]
distances = [
MinDistance(target=line, distance=distance(source_b, target_a), offset=1, reverse_target=False),
MinDistance(target=line, distance=distance(source_a, target_a), offset=-1, reverse_target=True),
MinDistance(target=line, distance=distance(source_b, target_b), offset=1, reverse_target=True),
MinDistance(target=line, distance=distance(source_a, target_b), offset=-1, reverse_target=False)
]
min_distance = min(distances, key=attrgetter('distance'))
min_distances.append(min_distance)
min_distance = min(min_distances, key=attrgetter('distance'))
target = min_distance.target
if min_distance.reverse_target:
target.reverse()
if min_distance.offset == 1:
sorted_multistring.append(target)
else:
sorted_multistring.insert(0, target)
lines.remove(target)
geometry['coordinates'] = sorted_multistring
return geometry
答案 2 :(得分:0)
即使这个问题需要一个Python解决方案,并且由于我通过DuckDuckGo搜索到达此页面并试图找到一种解决该问题的方法(对GeoJson MultiLineString几何体上的段进行排序),所以我认为Java / Kotlin解决方案可能会得到其他人的赞赏。
最初,我想出了自己的解决方案,该解决方案与@kissaprofeetta相似。尽管我在处理方法上有一些差异:
我添加了GeoJson文件的读取和排序后的GeoJson的文字
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import org.xml.sax.SAXException
import java.io.File
import java.io.IOException
import java.io.PrintWriter
import javax.xml.parsers.ParserConfigurationException
const val ADDITION_MODE_BEGINNING_TO_BEGINNING = 0
const val ADDITION_MODE_BEGINNING_TO_END = 1
const val ADDITION_MODE_END_TO_BEGINNING = 2
const val ADDITION_MODE_END_TO_END = 3
data class MultiLineString2(
val type: String,
val coordinates: ArrayList<ArrayList<ArrayList<Double>>>
)
fun sortSegmentsOfGeoJsonRoute(routeId: Int)
{
try {
val gson = GsonBuilder().setPrettyPrinting().create()
val geoJsonRoute = gson.fromJson(
File("src/main/assets/geojson/" + routeId + ".geojson").readText(),
MultiLineString2::class.java
)
val newTrackSegments = ArrayList<ArrayList<ArrayList<Double>>>()
newTrackSegments.add(geoJsonRoute.coordinates.first())
geoJsonRoute.coordinates.forEach { newSegment ->
if (!newTrackSegments.contains(newSegment)) {
var existingSegmentAsReference: ArrayList<ArrayList<Double>>? = null
var minDistanceToNewSegment = Double.MAX_VALUE
var additionMode = 0
newTrackSegments.forEach { existingSegment ->
val existingSegmentEnd = existingSegment.lastIndex
val newSegmentEnd = newSegment.lastIndex
val distFromBeginningToBeginning = distance(existingSegment[0][1], newSegment[0][1], existingSegment[0][0], newSegment[0][0])
val distFromBeginningToEnd = distance(existingSegment[0][1], newSegment[newSegmentEnd][1], existingSegment[0][0], newSegment[newSegmentEnd][0])
val distFromEndToBeginning = distance(existingSegment[existingSegmentEnd][1], newSegment[0][1], existingSegment[existingSegmentEnd][0], newSegment[0][0])
val distFromEndToEnd = distance(existingSegment[existingSegmentEnd][1], newSegment[newSegmentEnd][1], existingSegment[existingSegmentEnd][0], newSegment[newSegmentEnd][0])
var curMinDistance = Math.min(distFromBeginningToBeginning, distFromBeginningToEnd)
curMinDistance = Math.min(curMinDistance, distFromEndToBeginning)
curMinDistance = Math.min(curMinDistance, distFromEndToEnd)
if (curMinDistance <= minDistanceToNewSegment) {
minDistanceToNewSegment = curMinDistance
when (curMinDistance) {
distFromBeginningToBeginning -> additionMode = ADDITION_MODE_BEGINNING_TO_BEGINNING
distFromBeginningToEnd -> additionMode = ADDITION_MODE_BEGINNING_TO_END
distFromEndToBeginning -> additionMode = ADDITION_MODE_END_TO_BEGINNING
distFromEndToEnd -> additionMode = ADDITION_MODE_END_TO_END
}
existingSegmentAsReference = existingSegment
}
}
addTrackSegment(existingSegmentAsReference, additionMode, newSegment, newTrackSegments)
}
}
val sortedGeoJsonRoute = MultiLineString2("MultiLineString", newTrackSegments)
val geoJsonRouteWriter = PrintWriter("src/main/assets/geojson/" + routeId + "-sorted.geojson")
geoJsonRouteWriter.append(gson.toJson(sortedGeoJsonRoute))
geoJsonRouteWriter.close()
} catch (ex: ParserConfigurationException) { }
catch (ex: SAXException) { }
catch (ex: IOException) { }
catch (ex: Exception) {
print(ex.localizedMessage)
}
}
private fun addTrackSegment(
existingSegmentAsReference: ArrayList<ArrayList<Double>>?,
additionMode: Int,
newSegment: ArrayList<ArrayList<Double>>,
newTrackSegments: ArrayList<ArrayList<ArrayList<Double>>>
) {
if (existingSegmentAsReference != null) {
when (additionMode) {
ADDITION_MODE_BEGINNING_TO_BEGINNING -> {
val segmentToBeAdded = newSegment.reversed() as ArrayList<ArrayList<Double>>
val indexWhereToAddNewSegment = Math.max(0, newTrackSegments.indexOf(existingSegmentAsReference))
newTrackSegments.add(indexWhereToAddNewSegment, segmentToBeAdded)
}
ADDITION_MODE_BEGINNING_TO_END -> {
val indexWhereToAddNewSegment = Math.max(0, newTrackSegments.indexOf(existingSegmentAsReference))
newTrackSegments.add(indexWhereToAddNewSegment, newSegment)
}
ADDITION_MODE_END_TO_BEGINNING -> {
newTrackSegments.add(newSegment)
}
ADDITION_MODE_END_TO_END -> {
newTrackSegments.add(newSegment.reversed() as ArrayList<ArrayList<Double>>)
}
}
}
}
fun distance(lat1: Double, lat2: Double, lon1: Double, lon2: Double): Double
{
val earthRadius = 6371
val latDistance = Math.toRadians(lat2 - lat1)
val lonDistance = Math.toRadians(lon2 - lon1)
val a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2) + (Math.cos(Math.toRadians(lat1)) * Math.cos(
Math.toRadians(lat2)
) * Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2))
val c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
val distance = earthRadius.toDouble() * c * 1000.0
/* If you add el1 and el2 parameters, as elevation, then you coud make this change to take it in account for the distance. You'll have to remove, obviously, the return line that is now below this multiline comment
val height = el1 - el2
distance = Math.pow(distance, 2.0) + Math.pow(height, 2.0)
return Math.sqrt(distance) */
return Math.sqrt(Math.pow(distance, 2.0))
}