3

I have been working on getting leaflet (or openlayers) to be able to display S-57 data (also known as ENC) symbols (Known as S-52) onto the map. I know that leaflet can place svg data onto a map, but I haven't seen these symbols being used as anything else besides two formats. (An example of what these symbols look like can be found here: https://github.com/OpenCPN/OpenCPN/blob/master/data/s57data/rastersymbols-day.png)

The first example is one used from the following link: https://raw.githubusercontent.com/OpenCPN/OpenCPN/master/data/s57data/chartsymbols.xml

<symbol RCID="1268">
    <name>BOYBAR01</name>
    <description>barrel buoy, paper-chart</description>
    <bitmap width="19" height="14">
        <distance min="0" max="0" />
        <pivot x="11" y="12" />
        <origin x="0" y="0" />
        <graphics-location x="926" y="10" />
    </bitmap>
    <color-ref>CCHBLK</color-ref>
    <vector width="615" height="440">
        <distance min="0" max="0" />
        <pivot x="1500" y="1500" />
        <origin x="1145" y="1110" />
        <HPGL>SPC;PU1500,1500;SW2;CI50;PU1200,1500;PD1200,1345;PD1210,1280;PD1245,1210;PU1290,1165;PD1335,1140;PD1390,1120;PD1435,1110;PD1480,1110;PD1530,1120;PD1590,1155;PD1635,1185;PD1660,1215;PD1685,1265;PD1700,1310;PD1705,1345;PD1705,1500;PU1275,1175;PD1310,1230;PD1345,1280;PD1365,1335;PD1380,1410;PD1390,1455;PD1390,1500;PU1545,1500;PD1760,1500;PU1245,1210;PD1290,1165;PU1445,1500;PD1145,1500;</HPGL>
    </vector>
    <definition>V</definition>
</symbol>

In the case above the path is coded as HPGL. I have tried converting just the HPGL into an SVG, but the converter either fails or prints it upside down.

The other format is taken from S-52x.stylx (This is from ArcGIS, which if you have the ENC viewer extension install you can find here: C:\Users\<yourUsername>\ArcGIS\Runtime\Data\ENC\hydrography and you can load it with DB Browser for SQLite under the table SymbolInfo)

{
  "type": "CIMPointSymbol",
  "symbolLayers": [
    {
      "type": "CIMVectorMarker",
      "enable": true,
      "anchorPoint": {
        "x": 1.346457,
        "y": -4.818898,
        "z": 0.000000
      },
      "anchorPointUnits": "Absolute",
      "dominantSizeAxis3D": "Z",
      "offsetX": 0.000000,
      "rotateClockwise": true,
      "size": 12.472441,
      "billboardMode3D": "None",
      "frame": {
        "xmin": -10.062992,
        "ymin": -1.417323,
        "xmax": 7.370079,
        "ymax": 11.055118
      },
      "markerGraphics": [
        {
          "type": "CIMMarkerGraphic",
          "geometry": {
            "paths": [
              [
                [
                  1.417323,
                  0.000000
                ],
                [
                  1.403167,
                  -0.199814
                ],
                [
                  1.360984,
                  -0.395636
                ],
                [
                  1.291614,
                  -0.583555
                ],
                [
                  1.196445,
                  -0.759818
                ],
                [
                  1.077376,
                  -0.920904
                ],
                [
                  0.936787,
                  -1.063595
                ],
                [
                  0.777486,
                  -1.185040
                ],
                [
                  0.602654,
                  -1.282814
                ],
                [
                  0.415785,
                  -1.354964
                ],
                [
                  0.220610,
                  -1.400048
                ],
                [
                  0.021028,
                  -1.417167
                ],
                [
                  -0.178973,
                  -1.405977
                ],
                [
                  -0.375400,
                  -1.366704
                ],
                [
                  -0.564328,
                  -1.300130
                ],
                [
                  -0.741984,
                  -1.207586
                ],
                [
                  -0.904818,
                  -1.090921
                ],
                [
                  -1.049579,
                  -0.952464
                ],
                [
                  -1.173374,
                  -0.794982
                ],
                [
                  -1.273731,
                  -0.621621
                ],
                [
                  -1.348646,
                  -0.435842
                ],
                [
                  -1.396621,
                  -0.241358
                ],
                [
                  -1.416699,
                  -0.042052
                ],
                [
                  -1.408478,
                  0.158094
                ],
                [
                  -1.372123,
                  0.355082
                ],
                [
                  -1.308360,
                  0.544976
                ],
                [
                  -1.218462,
                  0.723986
                ],
                [
                  -1.104225,
                  0.888533
                ],
                [
                  -0.967932,
                  1.035332
                ],
                [
                  -0.812304,
                  1.161450
                ],
                [
                  -0.640450,
                  1.264369
                ],
                [
                  -0.455803,
                  1.342031
                ],
                [
                  -0.262052,
                  1.392886
                ],
                [
                  -0.063066,
                  1.415919
                ],
                [
                  0.137179,
                  1.410669
                ],
                [
                  0.334685,
                  1.377240
                ],
                [
                  0.525505,
                  1.316301
                ],
                [
                  0.705828,
                  1.229069
                ],
                [
                  0.872052,
                  1.117286
                ],
                [
                  1.020857,
                  0.983186
                ],
                [
                  1.149271,
                  0.829446
                ],
                [
                  1.254727,
                  0.659138
                ],
                [
                  1.335121,
                  0.475664
                ],
                [
                  1.388845,
                  0.282689
                ],
                [
                  1.417323,
                  0.000000
                ]
              ],
              [
                [
                  -8.503937,
                  0.000000
                ],
                [
                  -8.503937,
                  4.393701
                ],
                [
                  -8.220472,
                  6.236220
                ],
                [
                  -7.228346,
                  8.220472
                ]
              ],
              [
                [
                  -5.952756,
                  9.496063
                ],
                [
                  -4.677165,
                  10.204724
                ],
                [
                  -3.118110,
                  10.771654
                ],
                [
                  -1.842520,
                  11.055118
                ],
                [
                  -0.566929,
                  11.055118
                ],
                [
                  0.850394,
                  10.771654
                ],
                [
                  2.551181,
                  9.779528
                ],
                [
                  3.826772,
                  8.929134
                ],
                [
                  4.535433,
                  8.078740
                ],
                [
                  5.244094,
                  6.661417
                ],
                [
                  5.669291,
                  5.385827
                ],
                [
                  5.811024,
                  4.393701
                ],
                [
                  5.811024,
                  0.000000
                ]
              ],
              [
                [
                  -6.377953,
                  9.212598
                ],
                [
                  -5.385827,
                  7.653543
                ],
                [
                  -4.393701,
                  6.236220
                ],
                [
                  -3.826772,
                  4.677165
                ],
                [
                  -3.401575,
                  2.551181
                ],
                [
                  -3.118110,
                  1.275591
                ],
                [
                  -3.118110,
                  0.000000
                ]
              ],
              [
                [
                  1.275591,
                  0.000000
                ],
                [
                  7.370079,
                  0.000000
                ]
              ],
              [
                [
                  -7.228346,
                  8.220472
                ],
                [
                  -5.952756,
                  9.496063
                ]
              ],
              [
                [
                  -1.559055,
                  0.000000
                ],
                [
                  -10.062992,
                  0.000000
                ]
              ]
            ]
          },
          "symbol": {
            "type": "CIMLineSymbol",
            "symbolLayers": [
              {
                "type": "CIMSolidStroke",
                "enable": true,
                "capStyle": "Round",
                "joinStyle": "Bevel",
                "lineStyle3D": "Tube",
                "miterLimit": 10,
                "width": 2,
                "color": [
                  0,
                  0,
                  0,
                  255
                ]
              }
            ]
          }
        }
      ],
      "respectFrame": true
    }
  ],
  "haloSize": 1,
  "scaleX": 1,
  "angleAlignment": "Display"
}

Insight onto how I can get these to either convert them into svg's that I can then load into leaflet, or be able to draw them from the raw data.

Edit 1: If you want to see the docs on how these symbols are drawn view https://iho.int/uploads/user/pubs/standards/s-52/S-52%20PresLib%20Ed%204.0.2%20Part%20I%20Addendum.pdf That contains the standard for each symbol, to view the one I reference search for "BOYBAR01"

Edit 2: Another option could be to convert the data into a local tileset so I can load that into leaflet. I've tried a couple of tests to find software to convert the data, but I have found nothing so far (that works at least).

Edit 3: OpenCPN seems like a solution that could work, however I'm unsure how to install it, or how it works after it is installed. If this route ends up working I'll end up using within my nodejs application.

Edit 4: ESRI has a beta for their CIM data plotting found here: https://www.esri.com/arcgis-blog/products/js-api-arcgis/mapping/create-points-lines-and-polygons-using-cimsymbols/ As far as I have tested, this only works with their mapping platform. Since this is in beta there maybe symbols that don't work, but I haven't seen any that don't work from their list of styles from their database yet. The only problem is how to get this data from esri to work with leaflet. I know there is a esri-leaflet project, but this is just for their mapping platform for now. So another option would be to use this CIM specification to draw the data using some leaflet polylineDecorator or something along those lines. The only problem is, how do I even do that?

Edit 5: Looking into the way that openlayers works I have added the option for openlayers. From looking into the way that openlayers styles work, it would give me a lot more control over how the symbol is displayed, however some things like Stroke Pattern, which uses a canvas, would work well for a lot of things, however I am unsure how to rotated some of the data that needs to face inwards towards the polygons. Point symbols would be simple enough. Any advice on that would be helpful.

Manny K SoSo
  • 31
  • 1
  • 5

2 Answers2

1

Based on Mike Nunn's solution (https://gis.stackexchange.com/questions/306976/add-image-along-the-linestring/306979), the following code may solve your problem.

// Symbol

var img = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAADsQAAA7EB9YPtSQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAHzSURBVFiF7ZY9SBxBFMd/s7uHGIKoB4paJJIyJNjETjkRG7FIE5MlEEQJRAiEC1xhIG0QRIQIdp6gnQgipEqRSkgXDGiXwAUEOZDoJSx+3Mez8LKep3s6c3oWuX83O/P++5v3dt4sVFXVDUuZBk7yUpqoB8Bjn1dMG3lZJkEuEQlT549rqeE9rlQM4CHt2AWhFop7tJpYmQGMMaey5Pxxlhzf+FEZgEG65Tk9ckimwERxQJqndGuXQfvDWSBW8iUb/GKcxUv7OroACZIoFA42bYQBSOGxiwfAJttafsbHEE6yscJXllit3DEsloNtHFs2gCDYNwkA4JRhcyUA15IBl8ilz/S1ZOA+dxhn+EIIhSqZAZeIzJfoHYEAW/ymjTBxohdCBGXgA0PSzyMg2CIQIMkOGbKEcFggJqMMBLqEzulncd5Ka75RbfNHH2CJVWXlpwWhneYzayS/s2KAJ3RJCBsbC0FIkNQHOJ48bm4KRQuNvOPZqSz8AygsgUtEBuj0xwrFR1YCu2TJ9jlPTF4woYbok146/OdpsqTwaOA2NhZ7HJImQx23/DUpPF4zo2aJyghTZgDFihOV8+pdLJ0b0egCecNjecBdQjh+mf6yxzoJZvik5Vn2bZhDWOMnUywbeWn/DxTqC98BmONzWRup6v/WETwrgqYBFtclAAAAAElFTkSuQmCC"

// Helpers

function splitLineString(geometry, minSegmentLength, options) {

  function calculatePointsDistance(coord1, coord2) {
    var dx = coord1[0] - coord2[0];
    var dy = coord1[1] - coord2[1];
    return Math.sqrt(dx * dx + dy * dy);
  }

  function calculateSplitPointCoords(startNode, nextNode, distanceBetweenNodes, distanceToSplitPoint) {
    var d = distanceToSplitPoint / distanceBetweenNodes;
    var x = nextNode[0] + (startNode[0] - nextNode[0]) * d;
    var y = nextNode[1] + (startNode[1] - nextNode[1]) * d;
    return [x, y];
  }

  function calculateAngle(startNode, nextNode, alwaysUp) {
    var x = (startNode[0] - nextNode[0]);
    var y = (startNode[1] - nextNode[1]);
    var angle = alwaysUp ? Math.atan(x / y) : Math.atan2(x, y);
    return angle - Math.PI / 2 * orientation
  }

  var splitPoints = [];
  var coords = geometry.getCoordinates();
  var coordIndex = 0;
  var startPoint = coords[coordIndex];
  var nextPoint = coords[coordIndex + 1];
  var angle = calculateAngle(startPoint, nextPoint, options.alwaysUp);
  var n = Math.ceil(geometry.getLength() / minSegmentLength);
  var segmentLength = geometry.getLength() / n;
  var currentSegmentLength = options.midPoints ? segmentLength / 2 : segmentLength;

  for (var i = 0; i <= n; i++) {

    var distanceBetweenPoints = calculatePointsDistance(startPoint, nextPoint);
    currentSegmentLength += distanceBetweenPoints;

    if (currentSegmentLength < segmentLength) {
      coordIndex++;
      if (coordIndex < coords.length - 1) {
        startPoint = coords[coordIndex];
        nextPoint = coords[coordIndex + 1];
        angle = calculateAngle(startPoint, nextPoint, options.alwaysUp);
        i--;
        continue;
      } else {
        if (!options.midPoints) {
          var splitPointCoords = nextPoint;
          if (!options.extent || ol.extent.containsCoordinate(options.extent, splitPointCoords)) {
            splitPointCoords.push(angle);
            splitPoints.push(splitPointCoords);
          }
        }
        break;
      }
    } else {
      var distanceToSplitPoint = currentSegmentLength - segmentLength;
      var splitPointCoords = calculateSplitPointCoords(startPoint, nextPoint, distanceBetweenPoints, distanceToSplitPoint);
      startPoint = splitPointCoords.slice();
      if (!options.extent || ol.extent.containsCoordinate(options.extent, splitPointCoords)) {
        splitPointCoords.push(angle);
        splitPoints.push(splitPointCoords);
      }
      currentSegmentLength = 0;
    }
  }

  return splitPoints;
}

function calculateOrientation(coords) {
  var area = 0;
  for (var i = 0; i < coords.length; i++) {
    const addX = coords[i][0];
    const addY = coords[i === coords.length - 1 ? 0 : i + 1][1];
    const subX = coords[i === coords.length - 1 ? 0 : i + 1][0];
    const subY = coords[i][1];
    area += (addX * addY * 0.5) - (subX * subY * 0.5);
  }
  return Math.sign(area);
}

// StyleFunction

var styleFunction = function(feature, resolution) {

  var styles = [
    new ol.style.Style({
      stroke: new ol.style.Stroke({
        color: "darkmagenta",
        width: 2,
        lineDash: [4]
      })
    })
  ];

  var size = 64;
  var mapSize = map.getSize();
  var extent = map.getView().calculateExtent([mapSize[0] + (size * 2), mapSize[1] + (size * 2)]);

  var splitPoints = splitLineString(feature.getGeometry(), size * 2 * resolution, {
    alwaysUp: false,
    midPoints: true,
    extent: extent
  });

  splitPoints.forEach(function(point, index) {
    styles.push(new ol.style.Style({
      geometry: new ol.geom.Point([point[0], point[1]]),
      image: new ol.style.Icon({
        src: img,
        scale: 0.8,
        rotation: point[2]
      })
    }));

  });

  return styles;
}

// Layer

var raster = new ol.layer.Tile({
  source: new ol.source.OSM()
});
var vectorSource = new ol.source.Vector();
var vector = new ol.layer.Vector({
  source: vectorSource,
  style: styleFunction
});

// Map

var map = new ol.Map({
  layers: [raster, vector],
  target: "map",
  view: new ol.View({
    center: ol.proj.fromLonLat([7.5, 54.0]),
    zoom: 8
  })
});

// Interaction

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

var orientation;

draw.on("drawend", function(event) {
  var featureGeom = event.feature.getGeometry();
  if (featureGeom) {
    var featureCoords = featureGeom.getCoordinates();
    if (featureCoords.length > 0) {
      featureCoords.push(featureCoords[0])
      featureGeom.setCoordinates(featureCoords);
      orientation = calculateOrientation(featureCoords)
      // map.removeInteraction(draw);
    }
  }
})

map.addInteraction(draw);
html,
body,
.map {
  width: 100%;
  height: 100%;
  overflow: hidden;
}
<link href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.14.1/css/ol.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.14.1/build/ol.js"></script>
<div id="map" class="map" tabindex="0"></div>
  • Your answer could be improved with additional supporting information. Please [edit](https://stackoverflow.com/posts/72517845/edit) to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the [help center](https://stackoverflow.com/help/how-to-answer). – Ethan Jun 07 '22 at 20:30
  • This works pretty well and close to what I have tested (I didn't have an orientation function). The other option would potentially be in a future openlayers update [WebGL vector renderer for polygons, lines and points](https://github.com/openlayers/openlayers/pull/13461). I don't know 100% if placing images along lines/polygons would be possible, but it would massively help performance (since ENC/S57 haves tons of features). Overall Great answer! – Manny K SoSo Jun 16 '22 at 12:23
0

If you are looking for graphic files, this link may be of interest: OpenSeaMap

  • 1
    Please add more details. Don't just post a link – Charalamm Aug 01 '21 at 13:38
  • Those images will work well with point symbols, but the biggest problem is when I have a polygon or a line and I need a repeatable symbol to go along that line. I tried https://gis.stackexchange.com/questions/306976/add-image-along-the-linestring/306979 which works for lines, but getting this to work for polygons doesn't work 100% of the time (since I convert the polygon to a line) since the symbol should face towards the inside of the polygon. – Manny K SoSo Aug 02 '21 at 12:44