-1

My MultiLineString currently is styled like this:

'MultiLineString': new Style({
    stroke: new Stroke({
        color: 'green',
        width: 1
    })
}),

And the coordinates for my MultiLineString looks something like this:

[
[[3, 8], [4, 10], [5, 12]],
[[2, 4], [3, 8], [4, 10]]
]

where there is some overlap in the data. I want to scale the width of the line where the data overlaps to help illustrate that the line is being gone over multiple times. I'm guessing I need to utilize the RenderFunction(), but I'm having a hard time understanding what to do next.

Any help would be greatly appreciated.

rykener
  • 711
  • 1
  • 6
  • 16

2 Answers2

1

Yes, it can be done using Turf but Turf only works on EPSG:4326 coordinates and what overlaps in EPSG:4326 won't necessarily overlap when displayed in EPSG:3857 and vice versa, for example [[0,50],[5,55],[10,60]] isn't quite a straight line when transformed to EPSG:3857 while [[0,50],[10,60]] is. Transforms also introduce very small rounding differences. Although very small any difference will stop overlaps being detected (and using Turf's km tolerance setting can give false positives). This example does the extra iterations, and adds an extra component to your MultiLineString to test intersection between the vertices of longer line segments. After rounding to 12 places an overlap is detected in EPSG:4326 (with real world fractional values even that might not work) but if you zoom in enough you can see it is a parallel line in EPSG:3857. What is really needed is a function based on the Turf lineOverlap source which will work on any coordinates and handles mm tolerances appropriate to those coordinates.

var transformR = function(coordinates, output, dimensions) {
  var dims = dimensions || 2;
  for (var i=0; i<coordinates.length; i+=dims) {
coordinates[i] = Math.round(coordinates[i]*1e12)/1e12;
coordinates[i+1] = Math.round(coordinates[i+1]*1e12)/1e12;
  }
  return coordinates;
}

var style = function(feature) {

  switch(feature.getGeometry().getType()) {
case 'MultiLineString': 

  var increment = 2;
  var styles = [
    new ol.style.Style({
      stroke: new ol.style.Stroke({
        color: 'green',
        width: increment
      })
    })
  ];

  var overlaps = [];
  var format = new ol.format.GeoJSON();
  var geometry = feature.getGeometry().clone().transform(map.getView().getProjection(), 'EPSG:4326');
  geometry.applyTransform(transformR); // round transformed coordinates
  var linestrings = geometry.getLineStrings();
  for (var i=0; i<linestrings.length-1; i++) {
    for (var j=i+1; j<linestrings.length; j++) {
      var line1 = format.writeFeatureObject(new ol.Feature(linestrings[i]));
      var line2 = format.writeFeatureObject(new ol.Feature(linestrings[j]));
      var overlapping = turf.lineOverlap(line1, line2).features;
      overlapping.forEach(function(overlap){ overlaps.push(overlap.geometry.coordinates); });
    }
  }
  overlaps.forEach(function(overlap){
    var width = increment;
    var line = turf.lineString(overlap);
    for (var i=0; i<linestrings.length; i++) {
      var line1 = format.writeFeatureObject(new ol.Feature(linestrings[i]));
      var overlapping = turf.lineOverlap(line, line1).features;
      if (overlapping.length > 0 && JSON.stringify(overlapping[0].geometry.coordinates) == JSON.stringify(overlap)) {
        width += increment;
      }
    }
    styles.push(
      new ol.style.Style({
        geometry: new ol.geom.LineString(overlap).transform('EPSG:4326', map.getView().getProjection()),
        stroke: new ol.style.Stroke({
          color: 'green',
          width: width
        })
      })
    );
  });

  return styles;

  }

}

var multiline = new ol.Feature(new ol.geom.MultiLineString([
  [[6, 14], [2, 6]],
  [[3, 8], [4, 10], [5, 12]],
  [[2, 4], [3, 8], [4, 10]]
]));

var source = new ol.source.Vector();

var map = new ol.Map({
layers: [
    new ol.layer.Vector({
        source: source,
        style: style
    })
],
target: 'map',
view: new ol.View()
});

multiline.getGeometry().transform('EPSG:4326', map.getView().getProjection());
source.addFeature(multiline);

map.getView().fit(multiline.getGeometry());
<link href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" rel="stylesheet" />
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<script src="https://npmcdn.com/@turf/turf@5.1.6/turf.min.js"></script>

<div id="map" class="map"></div>
Mike
  • 16,042
  • 2
  • 14
  • 30
  • This solution does work for the problem provided. A problem I've run into with it is that it generates so many additional line segments that it cannot scale to large data sets. – rykener Mar 07 '19 at 03:28
  • Yes, there will be a lot duplication especially when the same segment overlaps many times (a factorial increase with each overlap?) which would place a heavy load on the styling/rendering. Extra code would be needed to find duplicate segments and only add the one with the greatest width to the styles. – Mike Mar 07 '19 at 11:47
0

The solution I've come up with is utilizing a function called lineOverlap in the library Turf. See the docs to that function here. The function takes in two line segments and returns a FeatureCollection that has the overlapping LineSegment in it.

import * as turf from '@turf/turf';
import GeoJSON from 'ol/format/GeoJSON.js';

const routes = [
    turf.lineString([[1,2],[3,4],[5,6]]),
    turf.lineString([[3,4],[5,6],[7,8]])
];
let overlapping = turf.lineOverlap(routes[0], routes[1]);

let format = new GeoJSON();

// convert to a format readable by OverLayers
let lines = format.readFeatures(overlapping);

// lines will only ever have a length of 1 or 0
if(lines.length) {

  // this adds an attribute onto the feature which I then read in the styling function
  lines[0].set('size', 2);

  lines[0].getGeometry().transform('EPSG:4326', 'EPSG:3857');
  vectorSource.addFeature(lines[0]);
}

adding multiple layers of iteration while scaling up the size should have the desired effect.

geocodezip
  • 158,664
  • 13
  • 220
  • 245
rykener
  • 711
  • 1
  • 6
  • 16
  • Transport routes often start and end on the same lines but can call at different local stops in between. So "lines will only ever have a length of 1 or 0" isn't true. In this case there would be two `[[1,2],[3,4],[5,6],[7,8],[9,10]], [[1,2],[3,4],[6,5],[7,8],[9,10]]` – Mike Mar 03 '19 at 12:23