如何对MultiLineString进行排序?

时间:2018-04-30 20:59:00

标签: javascript python geojson multilinestring

我有一个MultiLineString,它由构成路径的各个LineStrings组成。路径具有方向,并且必须对LineStrings进行排序以反映此顺序。要做到这一点,必须颠倒一些字符串以指向与其余字符串相同的方向。 什么是合适的算法来完成这项任务?

换句话说,对列表列表进行排序的最佳方法是什么,列表可以反转?即

输入:

[2, 1] [4, 5] [0, 1] [5, 6] [9, 8]

输出:

[0, 1] [1, 2] [4, 5] [5, 6] [8, 9] 

3 个答案:

答案 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相似。尽管我在处理方法上有一些差异:

  • 我使用的距离算法更加精确,并且旨在用于地理定位,因为它考虑到地球不是2D平面/地图,而是一个球体。实际上,海拔数据也可以轻松添加,甚至更加精确。
  • 将段添加到新的MultiLineString数组的方法比@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))
    }