Fractionnement de chaînes de lignes sur une ligne de données avec OpenLayers


9

Il y a quelques années, j'ai posté The International Date Line et @jdeolive a suggéré de diviser les fonctionnalités de la dateLine. J'ai donc essayé.

Lorsque j'essaie de diviser ma piste satellite avec splitWith sur la ligne de données, je reviens null. Je sais que je me sépare correctement parce que lorsque je me divise sur la ligne Greenwich, j'obtiens les résultats attendus.

Quelqu'un sait-il comment diviser correctement une Linestring par programmation le long de la ligne de date avec OpenLayers? Je cherche un exemple de code si vous l'avez.

J'ai essayé, wrapDateLinemais il ne semble pas fonctionner sur les calques vectoriels malgré le fait que mon calque vectoriel soit comme ceci:

vectorLayer = new OpenLayers.Layer.Vector("GroundTracks", {
    renderers: ['Canvas', 'VML'],
    wrapDateLine: true}); // <-- shoud be wraping.

entrez la description de l'image ici

Voici mon code:

var features = [];
var format = new OpenLayers.Format.WKT({
    'internalProjection': map.baseLayer.projection,
    'externalProjection': prjGeographic
});
var satTrack = format.read("LINESTRING (95.538611 13.286511, 94.730711 16.908947, 93.901095 20.528750, 93.043594 24.145177, 92.150978 27.757436, 91.214579 31.364666, 90.223791 34.965899, 89.165364 38.560019, 88.022401 42.145679, 86.772901 45.721205, 85.387568 49.284424, 83.826433 52.832413, 82.033480 56.361087, 79.927797 59.864504, 77.388419 63.333664, 74.227306 66.754285, 70.139140 70.102478, 64.605267 73.335774, 56.712904 76.373458, 44.881134 79.052803, 26.939886 81.047314, 02.704174 81.839241, -21.686285 81.101751, -39.887660 79.141947, -51.906937 76.480894, -59.912477 73.452897, -65.514482 70.225089, -69.645366 66.880243, -72.834535 63.461797, -75.393132 59.994131, -77.512464 56.491789, -79.315407 52.963919, -80.884039 49.416549, -82.275114 45.853820, -83.529088 42.278691, -84.675583 38.693355, -85.736827 35.099503, -86.729876 31.498490, -87.668095 27.891443, -88.562176 24.279331, -89.420849 20.663020, -90.251389 17.043303, -91.059999 13.420926, -91.852092 09.796602, -92.632515 06.171020, -93.405728 02.544857, -94.175960 -01.081217, -94.947343 -04.706542, -95.724045 -08.330456, -96.510402 -11.952298, -97.311065 -15.571400, -98.131162 -19.187081, -98.976502 -22.798638, -99.853829 -26.405335, -100.771148 -30.006378, -101.738172 -33.600889, -102.766925 -37.187866, -103.872602 -40.766117, -105.074803 -44.334175, -106.399366 -47.890158, -107.881153 -51.431559, -109.568417 -54.954914, -111.529886 -58.455253, -113.866668 -61.925160, -116.733085 -65.353081, -120.374635 -68.720132, -125.199754 -71.993642, -131.916790 -75.113368, -141.772276 -77.960803, -156.750096 -80.294831, -178.475596 -81.673196, 156.248392 -81.611421, 135.042323 -80.136505, 120.556535 -77.748172, 111.014840 -74.872356, 104.485504 -71.737081, 99.775637 -68.454400, 96.208126 -65.081545, 93.391438 -61.649716, 91.089380 -58.177038, 89.152970 -54.674643, 87.484294 -51.149703, 86.016609 -47.607042, 84.702947 -44.050030, 83.509299 -40.481112, 82.410411 -36.902133, 81.387093 -33.314533, 80.424442 -29.719485, 79.510644 -26.117981, 78.636145 -22.510889, 77.793053 -18.898997, 76.974710 -15.283040, 76.175371 -11.663718, 75.389950 -08.041709, 74.613831 -04.417680, 73.842693 -00.792294, 73.072378 02.833789, 72.298749 06.459907, 71.517566 10.085391, 70.724342 13.709564, 69.914194 17.331733, 69.081655 20.951185, 68.220447 24.567170, 67.323194 28.178891, 66.381031 31.785476, 65.383084 35.385943, 64.315735 38.979152, 63.161579 42.563725, 61.897893 46.137940, 60.494337 49.699551, 58.909396 53.245525, 57.084691 56.771602, 54.935577 60.271560, 52.334964 63.735923, 49.084320 67.149569, 44.859585 70.487030, 39.107498 73.702694, 30.852243 76.709182, 18.420695 79.329532, -00.339911 81.212453, -25.028018 81.831766)");

var featGreenwichLine = format.read("LINESTRING(0 -89, 0 89)");
var featDateLine = format.read("LINESTRING(180 -89, 180 89)");

features.push(featGreenwichLine);
features.push(featDateLine);
features.push(satTrack);

var resultsGreenwich = satTrack.geometry.splitWith(featGreenwichLine.geometry);
var resultsDateLine = satTrack.geometry.splitWith(featDateLine.geometry);

console.log(resultsGreenwich); //<--RETURNS EXPECTED RESULTS.
console.log(resultsDateLine);//<--RETURNS NULL.

vectorLayer.addFeatures(features);

Ma question n'est pas un double de cette question car ils veulent savoir comment le faire dans ogr2ogr

Mise à jour:

Voici à quoi ressemble un ensemble de données typique avec lequel je travaille (piste satellite 24 heures): Le wkt Linestring peut être trouvé ICI .

entrez la description de l'image ici


Quelle version d'Openlayers utilisez-vous?
Plux

@Plux 2.13.1 (Lastest)
CaptDragon

1
Selon leur API, wrapDateLine ne devrait être utilisé que sur la couche de base, il n'est donc pas étonnant qu'il ne fonctionne pas sur la couche vectorielle. Cependant, je ne sais pas comment le faire fonctionner sur la couche vectorielle. J'ai moi-même un problème similaire avec les multipolygones qui traversent la ligne de données.
Plux

1
@Plux découvrez ma solution
CaptDragon

Belle solution. Ce n'est malheureusement pas applicable à mon problème, je pense que mon problème est plus du côté du géoserveur, et mon polygone contient des coordonnées qui sont de "l'autre côté" de la ligne de données, comme -180.00000000000003 90.00000190734869, ce qui crée des problèmes dans le géoserveur je crois. Je n'ai pas de problème pour afficher ceci sur ma carte (j'ai un wms qui fait ça), mais je veux les utiliser comme boîtes à filtres sur une requête wfs pour geoserver :)
Plux

Réponses:


2

Le problème est que votre entité ne traverse pas la ligne de date du point de vue d'OpenLayers, donc votre ligne de séparation ne coupe pas votre entité. Exemple de vos données:

..., -178.475596 -81.673196, 156.248392 -81.611421,...

Vous passez de -178 à 156, et cela ne franchit pas la ligne de date du point de vue OpenLayers. Au lieu de diviser sur la ligne de date, vous devez fractionner sur votre valeur X minimale.

// Build the splitting line based on the min and max coordinates of the vector to split
var minX = 999999999;
var minY = -20037508.34 // minimum value of the spherical mercator projection
var maxY = 20037508.34  // maximum value of the spherical mercator projection
//Extract the minimum X from the data as bounds seems to be rounded.
for(var i=0; i<satTrack.geometry.components.length; i++) {
    if(satTrack.geometry.components[i].x < minX)
        minX = satTrack.geometry.components[i].x;
}
var pointList = [
    new OpenLayers.Geometry.Point(minX, minY),
    new OpenLayers.Geometry.Point(minX, maxY)
];
var featDateLine = new OpenLayers.Feature.Vector(
    new OpenLayers.Geometry.LineString(pointList)
);

J'ai construit un exemple ici qui a réussi à diviser votre piste satellite en 2 fonctionnalités: http://jsfiddle.net/6XJ5A/

Maintenant, pour utiliser le WKT avec plusieurs lignes dans votre mise à jour, au lieu d'utiliser une ligne droite, vous devez parcourir l'ensemble de données et créer votre ligne de séparation avec toutes les coordonnées qui traversent la ligne de données. En construisant une petite ligne à l'intérieur d'une multiligne, vous pouvez diviser toutes les coordonnées qui doivent traverser la ligne de données. Voici l'exemple mis à jour: http://jsfiddle.net/Jc274/

Et le code:

// Build the splitting line based on the min and max coordinates of the vector to split
var pointList = [];
var lastPoint = satTrack.geometry.components[0];
//Extract the minimum X from the data as bounds seems to be rounded.
for (var i = 1; i < satTrack.geometry.components.length; i++) {
    if (Math.abs(satTrack.geometry.components[i].x - lastPoint.x) > 10000000) {
        pointList.push(satTrack.geometry.components[i]);
    }
    lastPoint = satTrack.geometry.components[i];
}

var lineList = [];
for(var i=0; i<pointList.length; i++) {
    lineList.push(new OpenLayers.Geometry.LineString([
        new OpenLayers.Geometry.Point(pointList[i].x, pointList[i].y-0.00001), 
        new OpenLayers.Geometry.Point(pointList[i].x, pointList[i].y+0.00001)
    ]));
}

var featDateLine = new OpenLayers.Feature.Vector(
new OpenLayers.Geometry.MultiLineString(lineList), null, split_style);

Cela vous renverra une ligne divisée sur tous les points qui "traversent" la ligne de temps

Notez que je boucle également les coordonnées pour supprimer la ligne qui traverse la carte pour connecter les 2 coordonnées:

for (var i = 0; i < resultsDateLine.length; i++) {
    // Remove the first (or last) point of the line, the one that cross the dateline
    if (Math.abs(resultsDateLine[i].components[0].x - resultsDateLine[i].components[1].x) > 10000000) {
        resultsDateLine[i].removeComponent(resultsDateLine[i].components[0]);
    }
    if (Math.abs(resultsDateLine[i].components[resultsDateLine[i].components.length - 1].x - resultsDateLine[i].components[resultsDateLine[i].components.length - 2].x) > 10000000) {
        resultsDateLine[i].removeComponent(resultsDateLine[i].components[resultsDateLine[i].components.length - 1]);
    }
    features.push(new OpenLayers.Feature.Vector(resultsDateLine[i], null, style_array[i]));
}

Mise à jour: j'ai mis à jour le premier exemple pour n'ajouter que la ligne qui a été fractionnée. J'ai également mis à jour l'explication en conséquence. Cette approche n'est pas à l'épreuve des balles avec la piste satellite 24h que vous avez fournie, mais j'y travaille.

Mise à jour 2: j'ai mis à jour le deuxième exemple. En utilisant une multiligne pour diviser et parcourir le résultat pour supprimer les coordonnées supplémentaires ajoutées par la division, nous obtenons un ensemble de fonctionnalités qui ne traversent jamais la ligne de données.


+1 pour l'effort, mais vos exemples de carte ne semblent pas résoudre le problème. Les traces satellites sur votre exemple de carte traversent toujours le globe entier au lieu de suivre le chemin naturel de la trace satellite. J'ai dû louper quelque chose?
CaptDragon

Vous pourriez mal comprendre le problème car il ..., -178.475596 -81.673196, 156.248392 -81.611421,...traverse absolument la ligne de données. Voir ici
CaptDragon

Permettez-moi de mettre à jour le code et mon explication. Je sais que cela devrait traverser la ligne de données, mais OpenLayers ne le prend pas en charge. Du point de vue de l'OL, il ne traverse pas la ligne de temps.
Julien-Samuel Lacroix

C'est vrai. C'est le problème. Je vais devoir tromper OpenLayers et diviser la ligne de sorte qu'elle monte jusqu'au bord puis continue de l'autre côté où elle est censée le faire.
CaptDragon

2

J'ai trouvé une excellente solution sur le github de @Dane. Il s'appelle Arc.js et sert à calculer les itinéraires du Grand cercle. Non seulement cela, il divisera également la ligne sur la ligne de données et vous fournira deux chaînes de lignes qui se rencontrent sur la ligne de données, que OpenLayers peut facilement mapper. J'espère qu'il se présente pour réclamer la prime.

Voici mes résultats:

entrez la description de l'image ici entrez la description de l'image ici


1

La fonction splitWith ne connaît pas la forme tridimensionnelle de la Terre. Il ne fonctionne que dans un monde à deux dimensions. Dans votre cas, toutes vos LINESTRINGcoordonnées X sont comprises entre -180 et 180. Donc, du point de vue bidimensionnel d'OpenLayers, la chaîne de ligne ne traverse jamais réellement votre géométrie divisée (la ligne de date) et elle vous le dit en revenant null.

Je pense que vous devrez écrire du code personnalisé pour effectuer le fractionnement. J'imagine un algorithme qui boucle sur vos sommets, créant des chaînes de ligne de sortie comme ceci:

  • Pour chaque paire de sommets adjacente, décidez si le segment entre eux traverse la ligne de date.
  • Si ce n'est pas le cas, conservez ce segment tel qu'il est et ajoutez-le à la chaîne de ligne de sortie "actuelle".
  • Si c'est le cas , divisez le segment en deux parties. Ajoutez une partie à la chaîne de ligne "actuelle", démarrez une nouvelle chaîne de ligne "actuelle" et ajoutez l'autre partie à cette nouvelle.

Une heuristique raisonnable pour déterminer si une paire de sommets traverse la ligne de date est de voir si la différence entre les coordonnées X est supérieure à 180 degrés. (Bien que cela puisse se tromper, par exemple, dans les régions polaires. Peut-être avez-vous la chance de ne pas avoir de latitudes vraiment élevées.)

L'opération de division d'un segment en deux parties peut être aussi simple qu'une interpolation linéaire (si vous ne vous souciez pas trop de la précision de la piste). Lorsque vous détectez que le segment traverse la ligne de date, vous faites une copie du deuxième sommet et déplacez sa coordonnée X (en ajoutant ou en soustrayant 360 degrés), puis interpolez la coordonnée Y.

EDIT : Voici un JSFiddle qui démontre l'algorithme ci-dessus sur vos données: http://jsfiddle.net/85vjS/


0

Si cela fonctionne avec Greenwich, c'est parce que vous êtes dans les limites de votre CRS. Je suggère donc d'abord la même solution de contournement que dans le post que vous pointez:

var featDateLine = format.read("LINESTRING(179.99 -89, 179.99 89)");

et peut-être

var featDateLine = format.read("LINESTRING(-179.99 -89, -179.99 89)");

pour l'autre côté.

Une autre solution consiste à travailler dans un CRS qui n'est pas «hors limites» au niveau de la ligne de données. Vous devriez alors pouvoir diviser vos données sans problème.


J'ai déjà essayé les deux LINESTRING(179.99 -89, 179.99 89)et LINESTRING(-179.99 -89, -179.99 89)en vain. En ce qui concerne le CRS, malheureusement, cela ne fonctionnera pas pour mon objectif car je mappe des pistes satellites qui font le tour du monde de nombreuses fois. Donc, tous les CRS sont divisés quelque part et j'aurai le même problème partout où je le diviserai. Merci pour votre contribution.
CaptDragon
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.