Search code examples
javascriptmapsopenlayers

LineString direction arrows in Openlayers 4


I'm trying to make a LineString which has arrows in the end of each line to show direction of the route. I use an example from the official site: https://openlayers.org/en/latest/examples/line-arrows.html The example code creates arrows by user's drawing, but I need arrows for given LineString. My code contains icons for end and finish of the route. When I use

'route': new ol.style.Style({
      stroke: new ol.style.Stroke({
        width: 6, color: [23, 120, 22, 0.6]
      })
    }),

in styles, my code works. But when I put style for Linestring from the example, it gives me an error saying "Uncaught TypeError: c.Y is not a function".

Here is my code:

var points = [
  [76.8412, 43.2245], [76.8405, 43.2210], [76.8479, 43.2200], [76.8512, 43.2220]
];

var route = new ol.geom.LineString(points);
route.transform('EPSG:4326', 'EPSG:3857');

var routeFeature = new ol.Feature({
  type: 'route',
  geometry: route
});
var startMarker = new ol.Feature({
  type: 'icon-a',
  geometry: new ol.geom.Point(ol.proj.fromLonLat(points[0]))
});
var endMarker = new ol.Feature({
  type: 'icon-b',
  geometry: new ol.geom.Point(ol.proj.fromLonLat(points[points.length - 1]))
});

var styles = {
  'route': function(feature) {
    var geometry = feature.getGeometry();
    var styles = [
      // linestring
      new ol.style.Style({
        stroke: new ol.style.Stroke({
          color: '#ffcc33',
          width: 2
        }),
        image: new ol.style.Icon({
      anchor: [0.5, 1],
      src: 'img/icon-a.png'
    })
      })
    ];

    geometry.forEachSegment(function(start, end) {
      var dx = end[0] - start[0];
      var dy = end[1] - start[1];
      var rotation = Math.atan2(dy, dx);
      // arrows
      styles.push(new ol.style.Style({
        geometry: new ol.geom.Point(end),
        image: new ol.style.Icon({
          src: 'https://openlayers.org/en/v4.6.3/examples/data/arrow.png',
          anchor: [0.75, 0.5],
          rotateWithView: true,
          rotation: -rotation
        })
      }));
    });

    return styles;
  },
  'icon-a': new ol.style.Style({
    image: new ol.style.Icon({
      anchor: [0.5, 1],
      src: 'img/icon-a.png'
    })
  }),
  'icon-b': new ol.style.Style({
    image: new ol.style.Icon({
      anchor: [0.5, 1],
      src: 'img/icon-b.png'
    })
  })
};

var vectorLayer = new ol.layer.Vector({
  source: new ol.source.Vector({
    features: [routeFeature, startMarker, endMarker]
  }),
  style: function(feature) {
    return styles[feature.get('type')];
  }
});

var center = ol.proj.fromLonLat([76.8512, 43.2220]);
var map = new ol.Map({
  target: document.getElementById('map'),
  view: new ol.View({
    center: center,
    zoom: 15,
    minZoom: 2,
    maxZoom: 19
  }),
  layers: [
    new ol.layer.Tile({
      source: new ol.source.OSM()
    }),
    vectorLayer
  ]
});
#map {
  /* just for testing purposes */
  width: 100%;
  min-width: 100px;
  max-width: 500px;
  margin-top: 50px;
  height: 50px;
}
<link href="https://openlayers.org/en/v4.6.4/css/ol.css" rel="stylesheet"/>
<script src="https://openlayers.org/en/v4.6.4/build/ol-debug.js"></script>
<div id="map"></div>


Solution

  • Firstly, you can use ol-debug.js instead of ol.js, which is uncompressed and helps debugging. The exception you get is

    TypeError: style.getImage is not a function (Line 30443)

    You get that error because your styles object is mixed: some styles are functions, some are plain Style objects.

    You might think that OL can handle both, and you are normally right. However, you provide a function to vectorLayer, so OL detects that you provided a function and calls it. The return value of that function is expected to be a style object. But for route, that returns a function instead!

    So when OL calls

    style: function(feature) {
        return styles[feature.get('type')];
    }
    

    It gets styles for the types icon-a, icon-b, but an function for route. You need to enhance your style function to handle that special case:

    style: function(feature) {
      const myStyle = stylesMap[feature.get('type')];
      if (myStyle instanceof Function) {
        return myStyle(feature);
      }
      return myStyle;
    }
    

    PS: Using the same name for a variable twice (styles) is bad practice and can lead to weird bugs.

    Here is the runnable example:

    var points = [
      [76.8412, 43.2245],
      [76.8405, 43.2210],
      [76.8479, 43.2200],
      [76.8512, 43.2220]
    ];
    
    var route = new ol.geom.LineString(points);
    route.transform('EPSG:4326', 'EPSG:3857');
    
    var routeFeature = new ol.Feature({
      type: 'route',
      geometry: route
    });
    var startMarker = new ol.Feature({
      type: 'icon-a',
      geometry: new ol.geom.Point(ol.proj.fromLonLat(points[0]))
    });
    var endMarker = new ol.Feature({
      type: 'icon-b',
      geometry: new ol.geom.Point(ol.proj.fromLonLat(points[points.length - 1]))
    });
    
    var stylesMap = {
      'route': function(feature) {
        var geometry = feature.getGeometry();
        var styles = [
          // linestring
          new ol.style.Style({
            stroke: new ol.style.Stroke({
              color: '#ffcc33',
              width: 2
            }),
            image: new ol.style.Icon({
              anchor: [0.5, 1],
              src: 'img/icon-a.png'
            })
          })
        ];
    
        geometry.forEachSegment(function(start, end) {
          var dx = end[0] - start[0];
          var dy = end[1] - start[1];
          var rotation = Math.atan2(dy, dx);
          // arrows
          styles.push(new ol.style.Style({
            geometry: new ol.geom.Point(end),
            image: new ol.style.Icon({
              src: 'https://openlayers.org/en/v4.6.5/examples/data/arrow.png',
              anchor: [0.75, 0.5],
              rotateWithView: true,
              rotation: -rotation
            })
          }));
        });
    
        return styles;
      },
      'icon-a': new ol.style.Style({
        image: new ol.style.Icon({
          anchor: [0.5, 1],
          src: 'img/icon-a.png'
        })
      }),
      'icon-b': new ol.style.Style({
        image: new ol.style.Icon({
          anchor: [0.5, 1],
          src: 'img/icon-b.png'
        })
      })
    };
    
    var vectorLayer = new ol.layer.Vector({
      source: new ol.source.Vector({
        features: [routeFeature, startMarker, endMarker]
      }),
      style: function(feature) {
        const myStyle = stylesMap[feature.get('type')];
        if (myStyle instanceof Function) {
          return myStyle(feature);
        }
        return myStyle;
      }
    });
    
    var center = ol.proj.fromLonLat([76.8512, 43.2220]);
    var map = new ol.Map({
      target: document.getElementById('map'),
      view: new ol.View({
        center: center,
        zoom: 15,
        minZoom: 2,
        maxZoom: 19
      }),
      layers: [
        new ol.layer.Tile({
          source: new ol.source.OSM()
        }),
        vectorLayer
      ]
    });
    html,
    body {
      width: 100%;
      height: 100%;
      padding: 0px;
      margin: 0px;
    }
    
    #map {
      /* just for testing purposes */
      width: 100%;
      height: 100%;
    }
    <link href="https://openlayers.org/en/v4.6.5/css/ol.css" rel="stylesheet" />
    <script src="https://openlayers.org/en/v4.6.5/build/ol-debug.js"></script>
    <div id="map"></div>