Take the 2-minute tour ×
Geographic Information Systems Stack Exchange is a question and answer site for cartographers, geographers and GIS professionals. It's 100% free, no registration required.

I am trying to make a OpenLayers measure tool that displays the length of each segment on top of each segment and the total length of the measurement as shown below:

Measure control with length of each segment

I have used this example http://openlayers.org/dev/examples/measure.html. I can see in the example that the geometry created is one big LineString.

Therefore my general thought was to get the measure control to create a new line segment (LineString) for each of the user click except the first, then add an attribute with the segment length and display it with the label parameter in the style.

But I don't know if it will conflict with the current design of the measure control or if this is the correct approach to my problem?

All comments or guidance would be appreciated.

share|improve this question

2 Answers 2

up vote 7 down vote accepted

Your suggestions sounds fine. Also every geometry object has a distanceTo() function which can be used like:

geometry.distanceTo(anothergeometry)

EDIT:

After more thought and testing the example, i think it would be easiest for you add new layer for labeling purposes. Possibly a layer of just points which you would set the labelXOffset and labelYOffset to half the distance between each point in your measuring linestring. But i don't think you'll be able to rotate the labels.

Look at how this measure tool is, it's probably the best you'll be able to do: http://chamaps.com/watervliet/

Check out how he does it: http://chamaps.com/watervliet/js/map.js

var lineStyle = new OpenLayers.Style();
lineStyle.addRules([
new OpenLayers.Rule({
    symbolizer: linemeasureStyles
})]);
var linemeasureStyleMap = new OpenLayers.StyleMap({
    "default": lineStyle
});
var length = new OpenLayers.Control.Measure(OpenLayers.Handler.Path, {
    displaySystem: "english",
    geodesic: true,
    persist: true,
    handlerOptions: {
        layerOptions: {
            styleMap: linemeasureStyleMap
        }
    },
    textNodes: null,
    callbacks: {
        create: function () {
            this.textNodes = [];
            layerRuler.removeFeatures(layerRuler.features);
            mouseMovements = 0;
        },
        modify: function (point, line) {
            if (mouseMovements++ < 5) {
                return;
            }
            var len = line.geometry.components.length;
            var from = line.geometry.components[len - 2];
            var to = line.geometry.components[len - 1];
            var ls = new OpenLayers.Geometry.LineString([from, to]);
            var dist = this.getBestLength(ls);
            if (!dist[0]) {
                return;
            }
            var total = this.getBestLength(line.geometry);
            var label = dist[0].toFixed(2) + " " + dist[1];
            var textNode = this.textNodes[len - 2] || null;
            if (textNode && !textNode.layer) {
                this.textNodes.pop();
                textNode = null;
            }
            if (!textNode) {
                var c = ls.getCentroid();
                textNode = new OpenLayers.Feature.Vector(
                new OpenLayers.Geometry.Point(c.x, c.y), {}, {
                    label: '',
                    fontColor: "#FF0000",
                    fontSize: "14px",
                    fontFamily: "Arial",
                    fontWeight: "bold",
                    labelAlign: "cm"
                });
                this.textNodes.push(textNode);
                layerRuler.addFeatures([textNode]);
            }
            textNode.geometry.x = (from.x + to.x) / 2;
            textNode.geometry.y = (from.y + to.y) / 2;
            textNode.style.label = label;
            textNode.layer.drawFeature(textNode);
            this.events.triggerEvent("measuredynamic", {
                measure: dist[0],
                total: total[0],
                units: dist[1],
                order: 1,
                geometry: ls
            });
        }
    }
});
share|improve this answer

had the same problem and found no tools that would give me the solution, so what i did was take the LineString (which is not the feature, but the feature's geometry => feature.geometry), get its vertices and store them in an array (points)

function onFeatureSelectLineString(event) {
    var feature = event.feature;
    var points = feature.geometry.getVertices();

i then store whatever data from segment as i find relevant (distance, speed, start/end times [mine is a tracking application]) in a 2dimensional array segmentdataArray[segmentCount][attribute] or, if you just want distance, segmentdataArray[segmentCount]. depending on the number of total points it might or might not be necessary to save data in array (few points, no need to bother, a little overhead wont be a serious problem).

var segmentdataArray  = new Array(points.length-1);
for (var i=0;i<(points.length);i++) { //skip first point
    segmentdataArray[i-1] = point[i-1].distanceTo(point[i-1]); // start at segmentdataArray[0]
}

so, now that we have our data, we need to know which segment the user clicked on. unfortunately, i found no native method, function or property that gives this info (please please please correct me if im wrong).

my approach was to store the current mouse position in "global" variables (declared outside function init(), available to all javascript functions in that script).

var mouseLat, mouseLon;

once inside the onFeatureSelect() function, i create a point from global lat + lon (mouse position at click, which is CLOSE TO but not necessarily ON the LineString) and calculate the distanceTo the LineString geometry

var clickPoint = new OpenLayers.Geometry.Point(mouseLon, mouseLat);
// shortest distance from clicked point to LineString geometry
var distance = feature.geometry.distanceTo(clickPoint);

i then create a 4-sided, "square" LineString* lineStringTemp around the mouseclick and intersect (splitWIth) the original feature.geometry (LineString). the result of a split is a null array (if geometries dont touch), or an array with at least 2 geometries, but usually 3 linestrings, since my box splits original LineString into 2 large linestrings and a very small one that is "inside" my square.

var splitFeatures = feature.geometry.splitWith(lineStringTemp);

if your box is too large, you will have more than 3 segments, one is created for each additional point where both geometries intersect. you may or may not invest time in result verification (resplit with smaller box if you get more than 4 geometries as the result of a split).

for 99% of cases, a square around the mouseclick point with sides 1.42 times ("go play with your triangles, euclid") the smallest distance between mouseclick and linestring will do the trick nicely. the only problem is that you need to convert distance to degrees to be able to create this sqaure.

latitudes are parallel and the distance between each degree is always 111.2 km. as to longitude, 1 degree longitude may represent 9000+ km at the equator as opposed to a few centimeters when standing "on" either pole (north or south).

// distance between degrees Lat = 111.2km (constant - lats are parallel to each other)
var latDegreeKm = 111.2;
// distance between degrees Lon vary (much smaller closer to poles)
var pointTemp = new OpenLayers.Geometry.Point((mouseLon+1), mouseLat);
var lonDegreeKm = pointTemp.distanceTo(clickPoint);
// 1.42:1 = approx relation between legs / hypothenuse in isosceles rectangle (90-45-45) triangle
var degreesLat = 1.42 * distance / latDegreeKm;
var degreesLon = 1.42 * distance / lonDegreeKm;
var lineStringTemp = new OpenLayers.Geometry.LineString([   
    new OpenLayers.Geometry.Point((mouseLon+lonDegreeKm), (mouseLat+degreesLat)), 
    new OpenLayers.Geometry.Point((mouseLon-lonDegreeKm), (mouseLat+degreesLat)), 
    new OpenLayers.Geometry.Point((mouseLon-lonDegreeKm), (mouseLat-degreesLat)), 
    new OpenLayers.Geometry.Point((mouseLon+lonDegreeKm), (mouseLat-degreesLat)) 
]); 

i then take the first split linestring (splitFeatures[0]) and count its points. i now know where the split occured (pointsTemp.length - 2 is the pointer to segmentdataArray[], if you are using it, in this example i am not)

var splitFeatures = feature.geometry.splitWith(lineStringTemp);
if (splitFeatures) {
    var attributes = {name: "FirstSegment", type: "LineString"};
    var pointsTemp = splitFeatures[0].getVertices();
    var pointsTempCount = pointsTemp.length;
    var lineStringSegment = new OpenLayers.Geometry.LineString([points[pointsTempCount-2], points[pointsTempCount-1] ]);
    var lineStringSegmentLength = lineStringSegment.getLength();
    var featureTemp = new OpenLayers.Feature.Vector(lineStringSegment, attributes);
    //var featureTemp = new OpenLayers.Feature.Vector(splitFeatures[0], attributes);  // alternate splits linestring and shows first part
    layerTemp.addFeatures(featureTemp);
    content += "\n\n Segment Length: "+lineStringSegmentLength+"\n  splitFeatures count: "+splitFeatures.length+"\n\n  featureTemp.geometry: "+featureTemp.geometry+"\n\n feature.geometry: "+feature.geometry;

    popup = new OpenLayers.Popup.FramedCloud("chicken", 
                             featureTemp.geometry.getBounds().getCenterLonLat(),
                             new OpenLayers.Size(100,100),
                             content,
                             null, true, onFeatureUnselect);
    popup.setBackgroundColor('white');
    featureTemp.popup = popup;
    map.addPopup(popup);
} else {
    alert( "no SPLIT OCCURED");
}

attention, if you start splitting and displaying lines, you will get an array populated with geometries which must be converted into features, before being placed on a layer and displayed on map as seen above in:

var featureTemp = new OpenLayers.Feature.Vector(splitFeatures[0], attributes);  
layerTemp.addFeatures(featureTemp);

note 2 self: see code zMapaAlunosCaminho06a.php

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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