0

I need to be able to draw a multi-linestring in OpenLayers 5 (this is easy enough) with customizable line ends.

For instance if I'm creating a ruler tool I want the ends of the multi-linestring to be a "T" to show precisely where the line starts and ends. The "ticks" on the end of the line need to be part of the geometry of the line.

I've constructed my line like this...

  const draw: olDraw = new olDraw({
  source: MapValues.drawSource,
  type: 'LineString',
  style: new olStyle({
    fill: new olFill({
      color: 'rgba(255, 255, 255, 0.2)'
    }),
    stroke: new olStroke({
      color: '#ffcc33',
      width: 2
    }),
    image: new CircleStyle({
      radius: 7,
      fill: new olFill({
        color: '#ffcc33'
      })
    })
  })
})
draw.on('drawstart', (e: olDrawEvent) => {
  const tool = this;
  let dStartMeasureTT = this.measureTooltip;
  // set sketch
  this.sketch = e.feature;
  var tooltipCoord = e.coordinate;
  tool.listener = this.sketch.getGeometry().on('change', function (evt) {
    var geom = evt.target;
    var output;
    output = tool.formatLength(geom);
    tooltipCoord = geom.getLastCoordinate();
    tool.measureTooltipElement.innerHTML = output;
    tool.measureTooltip.setPosition(tooltipCoord);
  });
});
draw.on('drawend', (e: olDrawEvent) => {
  const format: olGeoJson = new olGeoJson();
  this.shapeString = format.writeGeometry(e.feature.getGeometry(),
    { dataProjection: 'EPSG:4326', featureProjection: 'EPSG:3857', rightHanded: false });
  this.featureGeometry = transform(e.feature.getGeometry().getCoordinates(), 'EPSG:3857', 'EPSG:4326');
  // Pull up Create object modal
  const initialState = {
    message: '',
    title: 'Ruler Label',
    iconSize: 'xx-large',
    iconType: 'error',
    fontSize: 'x-large',
    inCharLimit: 50,
    inObjName: this.measureTooltipElement.innerHTML
  };
  this.bsModalRef = this.modalService.show(CreateobjectComponent, Object.assign({ class: 'modal-sm 
  modal-dialog-centered', initialState }, this.config));
  this.bsModalRef.content.closeBtnName = 'OK';
  this.bsModalRef.content.modalProcess.subscribe((objName) => {
    if (objName) {
      this.saveRulerToDb(objName);
      this.createMeasureTooltip();
    }
  });
});
map.addInteraction(draw);
}
}
Funn_Bobby
  • 647
  • 1
  • 20
  • 57
  • 1
    Use a style function similar to https://openlayers.org/en/latest/examples/line-arrows.html except only put icons (or a regular shape) at the start and end of the line, not individual segments – Mike Feb 25 '21 at 20:52

2 Answers2

4

You can convert the drawn LineString geometry to a MultiLineString in the drawend event then append start and end lines to it.

var raster = new ol.layer.Tile({
  source: new ol.source.OSM(),
});

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

var vector = new ol.layer.Vector({
  source: source,
});

var map = new ol.Map({
  layers: [raster, vector],
  target: 'map',
  view: new ol.View({
    center: [-11000000, 4600000],
    zoom: 4,
  }),
});

var draw = new ol.interaction.Draw({
  source: source,
  type: 'LineString',
});

draw.on('drawend', function(e) {
  var lineString = e.feature.getGeometry();
  var multiLineString = new ol.geom.MultiLineString([]);
  multiLineString.appendLineString(lineString);
  var size = lineString.getLength() / 20; // or use a fixed size if you prefer
  var coords = lineString.getCoordinates();
  // start
  var dx = coords[1][0] - coords[0][0];
  var dy = coords[1][1] - coords[0][1];
  var rotation = Math.atan2(dy, dx);
  var startLine = new ol.geom.LineString([
    [coords[0][0], coords[0][1] - size],
    [coords[0][0], coords[0][1] + size]
  ]);
  startLine.rotate(rotation, coords[0]);
  // end
  var lastIndex = coords.length - 1;
  var dx = coords[lastIndex - 1][0] - coords[lastIndex][0];
  var dy = coords[lastIndex - 1][1] - coords[lastIndex][1];
  var rotation = Math.atan2(dy, dx);
  var endLine = new ol.geom.LineString([
    [coords[lastIndex][0], coords[lastIndex][1] - size],
    [coords[lastIndex][0], coords[lastIndex][1] + size]
  ]);
  endLine.rotate(rotation, coords[lastIndex]);
  multiLineString.appendLineString(startLine);
  multiLineString.appendLineString(endLine);
  e.feature.setGeometry(multiLineString);
});

map.addInteraction(draw);
/* Always set the map height explicitly to define the size of the div
       * element that contains the map. */

.map {
  width: 100%;
  height: 100%;
}


/* Optional: Makes the sample page fill the window. */

html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <!--
from How to set different colors for a lineString OpenLayers 5
https://stackoverflow.com/questions/66351764/how-to-set-different-colors-for-a-linestring-openlayers-5
https://codesandbox.io/s/gpx-forked-6wpdy
-->
  <meta charset="UTF-8">
  <title>LineString T-ends</title>
  <!-- Pointer events polyfill for old browsers, see https://caniuse.com/#feat=pointer -->
  <script src="https://unpkg.com/elm-pep"></script>
</head>

<body>
  <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
  <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/build/ol.js"></script>
  <link rel="stylesheet" href="https://openlayers.org/en/v6.5.0/css/ol.css" type="text/css">
  <div id="map" class="map"></div>
</body>

</html>
Mike
  • 16,042
  • 2
  • 14
  • 30
1
  1. Make an icon which is the crossbar for the end of the line:
crossbar icon
"crossbar" icon
  1. add that to the ends of the LineStrings:
var styleFunction = function (feature) {
  var geometry = feature.getGeometry();
  var styles = [
    // linestring
    new ol.style.Style({
      stroke: new ol.style.Stroke({
        color: 'blue', // '#ffcc33',
        width: 2,
      }),
    }) ];
    var coords = geometry.getCoordinates();
    // start
    var dx = coords[1][0] - coords[0][0];
    var dy = coords[1][1] - coords[0][1];
    var rotation = Math.atan2(dy, dx);
    // start crossbar
    styles.push(
      new ol.style.Style({
        geometry: new ol.geom.Point(coords[0]),
        image: new ol.style.Icon({
          // url encoded svg icon to prevent cross-domain issues
          src: "data:image/svg+xml;utf8,%3Csvg width='30' height='40' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M 10 10 h 1 v 20 h -1 Z' fill='black' stroke='black'/%3E%3C/svg%3E",
          anchor: [10, 0.5],
          anchorXUnits: 'pixels',
          rotateWithView: true,
          rotation: -rotation,
        }),
      })
    );
    // end
    var lastIndex = coords.length-1;
    var dx = coords[lastIndex-1][0] - coords[lastIndex][0];
    var dy = coords[lastIndex-1][1] - coords[lastIndex][1];
    var rotation = Math.atan2(dy, dx);
    // end crossbar
    styles.push(
      new ol.style.Style({
        geometry: new ol.geom.Point(coords[lastIndex]),
        image: new ol.style.Icon({
          // url encoded svg icon to prevent cross-domain issues
          src: "data:image/svg+xml;utf8,%3Csvg width='30' height='40' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M 10 10 h 1 v 20 h -1 Z' fill='black' stroke='black'/%3E%3C/svg%3E",
          anchor: [9.5, 0.5],
          anchorXUnits: 'pixels',
          rotateWithView: true,
          rotation: -rotation,
        }),
      })
    );
  return styles;
};

live example

screenshot of map

code snippet:

var raster = new ol.layer.Tile({ // TileLayer({
  source: new ol.source.OSM(),
});

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

var styleFunction = function(feature) {
  var geometry = feature.getGeometry();
  var styles = [
    // linestring
    new ol.style.Style({
      stroke: new ol.style.Stroke({
        color: 'blue', // '#ffcc33',
        width: 2,
      }),
    })
  ];
  var coords = geometry.getCoordinates();
  // start
  var dx = coords[1][0] - coords[0][0];
  var dy = coords[1][1] - coords[0][1];
  var rotation = Math.atan2(dy, dx);
  // start crossbar
  styles.push(
    new ol.style.Style({
      geometry: new ol.geom.Point(coords[0]),
      image: new ol.style.Icon({
        // url encoded svg icon to prevent cross-domain issues
        src: "data:image/svg+xml;utf8,%3Csvg width='30' height='40' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M 10 10 h 1 v 20 h -1 Z' fill='black' stroke='black'/%3E%3C/svg%3E",
        anchor: [10, 0.5], // center vertically at end of line
        anchorXUnits: 'pixels',
        rotateWithView: true,
        rotation: -rotation,
      }),
    })
  );
  // end
  var lastIndex = coords.length - 1;
  var dx = coords[lastIndex - 1][0] - coords[lastIndex][0];
  var dy = coords[lastIndex - 1][1] - coords[lastIndex][1];
  var rotation = Math.atan2(dy, dx);
  // end crossbar
  styles.push(
    new ol.style.Style({
      geometry: new ol.geom.Point(coords[lastIndex]),
      image: new ol.style.Icon({
        // url encoded svg icon to prevent cross-domain issues
        src: "data:image/svg+xml;utf8,%3Csvg width='30' height='40' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M 10 10 h 1 v 20 h -1 Z' fill='black' stroke='black'/%3E%3C/svg%3E",
        anchor: [9.5, 0.5],
        anchorXUnits: 'pixels',
        rotateWithView: true,
        rotation: -rotation,
      }),
    })
  );
  return styles;
};
var lineString = new ol.geom.LineString([
  [-13015491.561823528, 5172360.467799401],
  [-12379535.48649086, 5182144.407419903]
])
// create the feature
var feature = new ol.Feature({
  geometry: lineString,
  name: 'Line'
});
source.addFeature(feature);

var vector = new ol.layer.Vector({ // VectorLayer({
  source: source,
  style: styleFunction,
});

var map = new ol.Map({
  layers: [raster, vector],
  target: 'map',
  view: new ol.View({
    center: [-11000000, 4600000],
    zoom: 4,
  }),
});

map.addInteraction(
  new ol.interaction.Draw({
    source: source,
    type: 'LineString',
  })
);
/* Always set the map height explicitly to define the size of the div
       * element that contains the map. */

.map {
  width: 100%;
  height: 100%;
}


/* Optional: Makes the sample page fill the window. */

html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <!--
from How to set different colors for a lineString OpenLayers 5
https://stackoverflow.com/questions/66351764/how-to-set-different-colors-for-a-linestring-openlayers-5
https://codesandbox.io/s/gpx-forked-6wpdy
-->
  <meta charset="UTF-8">
  <title>LineString T-ends</title>
  <!-- Pointer events polyfill for old browsers, see https://caniuse.com/#feat=pointer -->
  <script src="https://unpkg.com/elm-pep"></script>
</head>

<body>
  <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
  <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/build/ol.js"></script>
  <link rel="stylesheet" href="https://openlayers.org/en/v6.5.0/css/ol.css" type="text/css">
  <div id="map" class="map"></div>

  <script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
  </script>
  <script type="text/javascript">
    _uacct = "UA-162157-1";
    urchinTracker();
  </script>
</body>

</html>
geocodezip
  • 158,664
  • 13
  • 220
  • 245
  • This is great but is there a way to do it and have the whole thing a multilinestring with the ends being added lines and not icons? – Funn_Bobby Mar 02 '21 at 18:44