2

My goal to draw vector lines on top of a map that have directional 'arrowheads' along the direction of the line. The 'arrowhead' is really a style using a 'regular shape' of a triangle.

I want the map produced to have a larger size so that it approximates a resolution of 300 dpi (instead of the screen standard 96 dpi) so I can download that image and embed it in other applications where it will look like 300 dpi.

My code works for scaling up the original line but as for the 'arrowhead' triangle I can get it to increase it's size but it's no longer positioned at the center of my line segment, it stays at the center location of the line before it was scaled up.

Here's an example using a single line with an arrow, nothing else on the map.

First the html:

<!-- <!DOCTYPE html> -->
<html>
<head>
<script src="https://code.jquery.com/jquery-1.11.2.min.js"></script>

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/openlayers/4.2.0/ol.css" type="text/css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/openlayers/4.2.0/ol.js"></script>

</head>
<body>
    <div id="mapdiv">
        <div id="map" class="map" style="width: 400px; float: left; height: 400px;"></div>
    </div>
    <script type="text/javascript" src="js/map_canvas6.js"></script>
</body>
</html>

And the javascript:

var saveDPI = 300; // dpi of saved image
var zoomLevel = 17; // map zoom level
var pathColorRGBA = [ 0, 0, 255, 0.75 ]; // line color and opacity
var tangentSegmentLengthPct = 10; // percent of the length of the line used to
                                // calculate the tangent
var pathWidth = 2.0;  // line width

//function to change the map canvas scaling to get desired size for saved image
function setDPI(canvas, dpi) {
    var scaleFactor = dpi / 96;
    canvas.width = Math.ceil(canvas.width * scaleFactor);
    canvas.height = Math.ceil(canvas.height * scaleFactor);
    var ctx = canvas.getContext("2d");
    ctx.scale(scaleFactor, scaleFactor);
}

// function to calculate tangent to line to set arrow direction
function tangentToLine(geometry) {
    var ptLoc = 0.5;
    var ptDelta = 0.1;
    var coordCenter = geometry.getCoordinateAt(ptLoc);
    var coordBefore = geometry.getCoordinateAt(ptLoc - ptDelta / 100);
    var coordAfter = geometry.getCoordinateAt(ptLoc + ptDelta / 100);
    var dx = coordAfter[0] - coordBefore[0];
    var dy = coordAfter[1] - coordBefore[1];
    var rotation = Math.atan2(dy, dx);
    return rotation;
};

// function to create 'arrowhead' style to draw along line segment
function createArrowStyle(coordPt, radius, rotation, fillColor, strokeColor) {
    return new ol.style.Style({
        geometry : new ol.geom.Point(coordPt),
        image : new ol.style.RegularShape({
            fill : new ol.style.Fill({
                color : fillColor
            }),
            points : 3,  // triangle 'arrowhead'
            radius : radius,
            stroke : new ol.style.Stroke({
                color : strokeColor
            }),
            rotateWithView : false,
            rotation : -rotation + (Math.PI / 2)
        })
    });
}

// style of base line
var lineStyle = function lineStyle(width, color) {
    return new ol.style.Style({
        stroke : new ol.style.Stroke({
            color : color,
            width : width
        })
    });
};

// style function for the line segment feature
var styleFunction = function(feature, resolution) {
    var geom = feature.getGeometry();
    var styles = [ lineStyle(pathWidth, pathColorRGBA) ];
    var rotation = tangentToLine(geom);
    var radius = 10; // size of arrowhead
    var centerCoord = geom.getCoordinateAt(0.5);
    var arrowStyle = createArrowStyle(centerCoord, radius, rotation,
            pathColorRGBA, pathColorRGBA);
    // Here's where the arrowhead size is scaled up
    arrowStyle.getImage().setScale(saveDPI/96);
    styles.push(arrowStyle);
    return styles;
}

// map projection, a flat x-y plain
var proj = new ol.proj.Projection({
    code : 'EPSG: 404000',
    units : 'm'
});

// map viewport
var view = new ol.View({
    projection : proj,
    center : [ 400, 400 ],
    zoom : zoomLevel,
});

// map feature source
var source = new ol.source.Vector();

// make a simple straight line feature and add it to the source
var coordinates = [ [ 200, 200 ], [ 600, 600 ] ];
var line = new ol.geom.LineString(coordinates);

var feature = new ol.Feature({
    geometry : line,
});

source.addFeature(feature);

// make layer and add source and style function
var layer = new ol.layer.Vector({
    style : styleFunction,
    source : source
})

// create simple map
var map = new ol.Map({
    target : 'map',
    logo : false,
    layers : [ layer ],
    controls : [],
    interactions : ol.interaction.defaults({
        dragPan : false,
        mouseWheelZoom : false,
        doubleClickZoom : false
    }),
    view : view
});

// get map canvas
var canvas = $('canvas').get(0);

// increase the size of the map canvas so that when exported it looks like        300 dpi
map.on('precompose', function(event) {
    setDPI(canvas, saveDPI);
});

// draw map
map.renderSync();

The output looks like this (The arrowhead should be in the middle of the line) :

Line with arrowhead offset

I made a fiddle of the above it's at https://jsfiddle.net/rickblaine/z4y096so/

I expect there's some property of the style that will recenter the arrow in the center of the line but I haven't found it.

0

Instead of using a RegularShape style you can have a style with a polygon geometry which looks the same and scales properly when you increase the size of image.

The function looks like this:

function createArrowPolygonStyle(coordPt, radius, rotation, fillColor, strokeColor) {
    var endPt=[coordPt[0]+radius, coordPt[1]];
    var pt1 = endPt;
    var pt2 = [endPt[0] - radius*2, endPt[1] + radius];
    var pt3 = [endPt[0] - radius*2, endPt[1] - radius];
    var ring= [pt1,pt2,pt3,pt1];
    var poly = new ol.geom.Polygon([ring]);
    poly.rotate(rotation, coordPt)

    return new ol.style.Style({        
        geometry: poly,
        stroke : new ol.style.Stroke({
            color : strokeColor
        }),
        fill: fillColor
    });
}

Then in the function 'styleFunction' replace 'createArrowStyle' with 'createArrowPolygonStyle' and remove the line after it, i.e.

var styleFunction = function(feature, resolution) {
    var geom = feature.getGeometry();
    var styles = [ lineStyle(pathWidth, pathColorRGBA) ];
    var rotation = tangentToLine(geom);
    var radius = 10; // size of arrowhead
    var centerCoord = geom.getCoordinateAt(0.5);
    var arrowStyle = createArrowPolygonStyle(centerCoord, radius, rotation,
            pathColorRGBA, pathColorRGBA);
    styles.push(arrowStyle);
    return styles;
}

A revised fiddle at https://jsfiddle.net/rickblaine/z4y096so/1/

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.