Add support for ellipses in the geojson parser.
Specifically, if a polygon has been tagged as a ellipse or circle,
render it so.
MODIFY
src/annotation.js
src/annotation.js
@@ -1639,10 +1639,11 @@ var ellipseAnnotation = function (args, annotationName) {
style: $.extend(
{}, style,
{
- radius: radius + style.strokeWidth / 2,
+ radius: radius,
symbolValue: aspect,
rotation: rotation,
strokeOffset: 0,
+ radiusIncludesStroke: false,
scaleWithZoom: markerFeature.scaleMode.fill,
rotateWithMap: true,
strokeOpacity: style.stroke === false ? 0 : style.strokeOpacity,

MODIFY
src/annotationLayer.js
src/annotationLayer.js
@@ -564,30 +564,13 @@ var annotationLayer = function (arg) {
}
m_this.currentAnnotation = null;
}
- switch (m_mode) {
- case m_this.modes.edit:
+ if (m_mode === m_this.modes.edit) {
m_this.currentAnnotation = editAnnotation;
m_this.currentAnnotation.state(geo_annotation.state.edit);
m_this.modified();
- break;
- case 'line':
- createAnnotation = geo_annotation.lineAnnotation;
- break;
- case 'point':
- createAnnotation = geo_annotation.pointAnnotation;
- break;
- case 'polygon':
- createAnnotation = geo_annotation.polygonAnnotation;
- break;
- case 'rectangle':
- createAnnotation = geo_annotation.rectangleAnnotation;
- break;
- default:
- if (registry.registries.annotations[m_mode]) {
+ } else if (registry.registries.annotations[m_mode]) {
createAnnotation = registry.registries.annotations[m_mode].func;
}
- break;
- }
m_this.map().interactor().removeAction(
undefined, undefined, geo_annotation.actionOwner);
if (createAnnotation) {
@@ -708,7 +691,7 @@ var annotationLayer = function (arg) {
options.style = options.style || {};
options.labelStyle = options.labelStyle || {};
delete options.annotationType;
- // the geoJSON reader can only emit line, polygon, and point
+ // the geoJSON reader can emit line, polygon, point, and marker
switch (feature.featureType) {
case 'line':
position = feature.line()(data, data_idx);
@@ -733,10 +716,10 @@ var annotationLayer = function (arg) {
}
}
break;
- case 'point':
- position = [feature.position()(data, data_idx)];
- break;
case 'marker':
+ position = data.geometry.coordinates[0].slice(0, 4);
+ break;
+ case 'point':
position = [feature.position()(data, data_idx)];
break;
}
@@ -995,9 +978,9 @@ var annotationLayer = function (arg) {
$.each([
'closed', 'fill', 'fillColor', 'fillOpacity', 'line',
'lineCap', 'lineJoin', 'polygon', 'position', 'radius',
- 'rotateWithMap', 'rotation', 'scaleWithZoom', 'stroke',
- 'strokeColor', 'strokeOffset', 'strokeOpacity', 'strokeWidth',
- 'symbolValue', 'uniformPolygon'
+ 'radiusIncludesStroke', 'rotateWithMap', 'rotation',
+ 'scaleWithZoom', 'stroke', 'strokeColor', 'strokeOffset',
+ 'strokeOpacity', 'strokeWidth', 'symbolValue', 'uniformPolygon'
], function (keyidx, key) {
var origFunc;
if (feature.style()[key] !== undefined) {

MODIFY
src/geojsonReader.js
src/geojsonReader.js
@@ -31,6 +31,8 @@ var geojsonReader = function (arg) {
var $ = require('jquery');
var convertColor = require('./util').convertColor;
+ var markerFeature = require('./markerFeature');
+ var transform = require('./transform');
var m_this = this,
m_options = {
@@ -309,6 +311,22 @@ var geojsonReader = function (arg) {
*/
this.read = function (file, done, progress) {
var promise = new Promise(function (resolve, reject) {
+ /**
+ * Check if a feature is a circle or an ellipse.
+ *
+ * @param {geojson.object} f A geojson feature.
+ * @returns {boolean} true if this should be rendered as an ellipse or
+ * circle.
+ */
+ function _isEllipse(f) {
+ if (f.geometry.type !== 'Polygon' || f.geometry.coordinates.length !== 1 || f.geometry.coordinates[0].length !== 5) {
+ return false;
+ }
+ return (
+ (f.properties || {}).annotationType === 'ellipse' ||
+ (f.properties || {}).annotationType === 'circle');
+ }
+
/**
* Given a parsed GeoJSON object, convert it into features on the
* reader's layer.
@@ -384,7 +402,7 @@ var geojsonReader = function (arg) {
}
// process polygons
- const polygons = features.filter(f => f.geometry.type === 'Polygon');
+ const polygons = features.filter(f => f.geometry.type === 'Polygon' && !_isEllipse(f));
if (polygons.length) {
feature = m_this.layer().createFeature('polygon');
if (feature) {
@@ -410,6 +428,55 @@ var geojsonReader = function (arg) {
allFeatures.push(feature);
}
}
+ // handle ellipses and circle
+ const ellipses = features.filter(_isEllipse);
+ if (ellipses.length) {
+ feature = m_this.layer().createFeature('marker');
+ if (feature) {
+ ellipses.forEach((d) => {
+ const map = m_this.layer().map();
+ const coord = transform.transformCoordinates(map.ingcs(), map.gcs(), d.geometry.coordinates[0]);
+ const w = ((coord[0][0] - coord[1][0]) ** 2 + (coord[0][1] - coord[1][1]) ** 2) ** 0.5;
+ const h = ((coord[0][0] - coord[3][0]) ** 2 + (coord[0][1] - coord[3][1]) ** 2) ** 0.5;
+ const radius = Math.max(w, h) / 2 / map.unitsPerPixel(0);
+ const aspect = w ? h / w : 1e20;
+ const rotation = -Math.atan2(coord[1][1] - coord[0][1], coord[1][0] - coord[0][0]);
+ const pos = transform.transformCoordinates(map.gcs(), map.ingcs(), {
+ x: (coord[0][0] + coord[1][0] + coord[2][0] + coord[3][0]) / 4,
+ y: (coord[0][1] + coord[1][1] + coord[2][1] + coord[3][1]) / 4
+ });
+ d._props = {
+ pos: pos,
+ radius: radius,
+ aspect: aspect,
+ rotation: rotation
+ };
+ });
+ feature
+ .data(ellipses)
+ .position((d) => d._props.pos)
+ // create an object with each property in m_options.polygonStyle,
+ // mapping the values through the _style function.
+ .style(
+ [{}].concat(Object.keys(m_options.polygonStyle)).reduce(
+ (styleObj, key) => ({
+ [key]: ellipses.some(d => d.properties && d.properties[key] !== undefined) ?
+ m_this._style(key, m_options.polygonStyle[key]) :
+ m_options.polygonStyle[key],
+ radius: (d) => d._props.radius,
+ radiusIncludesStroke: false,
+ symbolValue: (d) => d._props.aspect,
+ rotation: (d) => d._props.rotation,
+ strokeOffset: 0,
+ rotateWithMap: true,
+ scaleWithZoom: markerFeature.scaleMode.fill,
+ ...styleObj
+ }
+ ))
+ );
+ allFeatures.push(feature);
+ }
+ }
if (done) {
done(allFeatures);
}

MODIFY
src/markerFeature.js
src/markerFeature.js
@@ -21,7 +21,8 @@ var pointFeature = require('./pointFeature');
* @property {number|function} [radius=5] Radius of each marker in pixels.
* This includes the stroke width if `strokeOffset` is -1, excludes it if
* `strokeOffset` is 1, and includes half the stroke width if `strokeOffset`
- * is 0.
+ * is 0. Note that is `radiusIncludesStroke` is `false`, this never
+ * includes the stroke width.
* @property {geo.geoColor|function} [strokeColor] Color to stroke each marker.
* @property {number|function} [strokeOpacity=1] Opacity for each marker's
* stroke. Opacity is on a [0-1] scale. Set this or `strokeWidth` to zero
@@ -32,6 +33,10 @@ var pointFeature = require('./pointFeature');
* @property {number|function} [strokeOffset=-1] The position of the stroke
* compared to the radius. This can only be -1, 0, or 1 (the sign of the
* value is used).
+ * @property {boolean|function} [radiusIncludeStroke=true] If truthy or
+ * undefined, the `radius` includes the `strokeWidth` based on the
+ * `strokeOffset`. If defined and falsy, the radius does not include the
+ * `strokeWidth`.
* @property {geo.geoColor|function} [fillColor] Color to fill each marker.
* @property {number|function} [fillOpacity=1] Opacity for each marker.
* Opacity is on a [0-1] scale. Set to zero to have no fill.
@@ -98,6 +103,7 @@ var markerFeature = function (arg) {
var pts, position,
radius = m_this.style.get('radius'),
strokeWidth = m_this.style.get('strokeWidth'),
+ radiusIncludesStroke = m_this.style.get('radiusIncludesStroke'),
strokeOffset = m_this.style.get('strokeOffset'),
scaleWithZoom = m_this.style.get('scaleWithZoom');
@@ -115,8 +121,10 @@ var markerFeature = function (arg) {
const r = radius(d, i),
s = strokeWidth(d, i),
so = Math.sign(strokeOffset(d, i));
- const rwiths = r + s * (so + 1) / 2, // radius with stroke
- rwos = r + s * (so - 1) / 2; // radius without stroke
+ let ris = radiusIncludesStroke(d, i);
+ ris = ris === undefined ? true : ris;
+ const rwiths = ris ? r + s * (so + 1) / 2 : r + s, // radius with stroke
+ rwos = ris ? r + s * (so - 1) / 2 : r; // radius without stroke
swz = markerFeature.scaleMode[swz] || (swz >= 1 && swz <= 3 ? swz : 0);
switch (swz) {
case markerFeature.scaleMode.stroke:
@@ -174,6 +182,7 @@ var markerFeature = function (arg) {
corners,
radius = m_this.style.get('radius'),
strokeWidth = m_this.style.get('strokeWidth'),
+ radiusIncludesStroke = m_this.style.get('radiusIncludesStroke'),
strokeOffset = m_this.style.get('strokeOffset'),
scaleWithZoom = m_this.style.get('scaleWithZoom');
@@ -223,7 +232,9 @@ var markerFeature = function (arg) {
swz = scaleWithZoom(data[i], i),
so = strokeOffset(data[i], i),
s = swz ? strokeWidth(data[i], i) : 0;
- var rwos = rad + s * (so - 1) / 2; // radius without stroke
+ let ris = radiusIncludesStroke(d, i);
+ ris = ris === undefined ? true : ris;
+ var rwos = ris ? rad + s * (so - 1) / 2 : rad; // radius without stroke
rad = rwos + s;
var p = m_this.position()(d, i),
dx, dy, rad2;
@@ -287,6 +298,7 @@ var markerFeature = function (arg) {
data = m_this.data(),
radius = m_this.style.get('radius'),
strokeWidth = m_this.style.get('strokeWidth'),
+ radiusIncludesStroke = m_this.style.get('radiusIncludesStroke'),
strokeOffset = m_this.style.get('strokeOffset'),
scaleWithZoom = m_this.style.get('scaleWithZoom'),
idx, min, max, corners,
@@ -347,7 +359,9 @@ var markerFeature = function (arg) {
swz = scaleWithZoom(data[i], i);
const so = strokeOffset(data[i], i),
s = swz ? strokeWidth(data[i], i) : 0;
- const rwos = rad + s * (so - 1) / 2; // radius without stroke
+ let ris = radiusIncludesStroke(d, i);
+ ris = ris === undefined ? true : ris;
+ const rwos = ris ? rad + s * (so - 1) / 2 : rad; // radius without stroke
swz = markerFeature.scaleMode[swz] || (swz >= 1 && swz <= 3 ? swz : 0);
rad = rwos + s;
switch (swz) {
@@ -393,6 +407,7 @@ var markerFeature = function (arg) {
{},
{
radius: 6.25,
+ radiusIncludesStroke: true,
strokeColor: { r: 0.851, g: 0.604, b: 0.0 },
strokeOffset: -1.0,
strokeOpacity: 1.0,

MODIFY
src/webgl/markerFeature.js
src/webgl/markerFeature.js
@@ -120,7 +120,8 @@ var webgl_markerFeature = function (arg) {
symbolValue: 1,
rotation: 1,
scaleWithZoom: 0,
- rotateWithMap: 0
+ rotateWithMap: 0,
+ radiusIncludesStroke: 0
},
vpf = m_this.verticesPerFeature(),
data = m_this.data(),
@@ -191,7 +192,7 @@ var webgl_markerFeature = function (arg) {
styleVal.scaleWithZoom +
(styleVal.rotateWithMap ? 4 : 0) +
// bit 3 reserved
- ((Math.sign(styleVal.strokeOffset) + 1) * 16) +
+ ((Math.sign(styleVal.radiusIncludesStroke !== undefined && styleVal.radiusIncludesStroke ? styleVal.strokeOffset : 1) + 1) * 16) +
styleVal.symbol * 64);
if (styleVal.symbolValue && styleVal.symbol >= markerFeature.symbols.arrow && styleVal.symbol < markerFeature.symbols.arrow + markerFeature.symbols.arrowMax) {
styleVal.symbolValue = packFloats(styleVal.symbolValue);

MODIFY
tests/cases/geojsonReader.js
tests/cases/geojsonReader.js
@@ -454,4 +454,55 @@ describe('geo.geojsonReader', function () {
done();
});
});
+
+ it('read ellipse and circle', function (done) {
+ var mockWebglRenderer = geo.util.mockWebglRenderer;
+ var restoreWebglRenderer = geo.util.restoreWebglRenderer;
+ var destroyMap = require('../test-utils').destroyMap;
+ mockWebglRenderer();
+ var map = createMap({center: [-105, 39], zoom: 3});
+ var layer = map.createLayer('feature', {renderer: 'webgl'});
+ var reader = geo.createFileReader('geojsonReader', {layer: layer});
+ var ellipseObj = {
+ features: [{
+ type: 'Feature',
+ properties: {
+ annotationType: 'ellipse'
+ },
+ geometry: {
+ type: 'Polygon',
+ coordinates: [[[-109, 41], [-102, 41], [-102, 37], [-109, 37], [-109, 41]]]
+ }
+ }, {
+ type: 'Feature',
+ properties: {},
+ geometry: {
+ type: 'Polygon',
+ coordinates: [[[-109, 41], [-102, 41], [-102, 37], [-109, 37], [-109, 41]]]
+ }
+ }, {
+ type: 'Feature',
+ properties: {
+ annotationType: 'circle'
+ },
+ geometry: {
+ type: 'Polygon',
+ coordinates: [[[-109, 41], [-102, 41], [-102, 37], [-109, 37], [-109, 41]]]
+ }
+ }],
+ type: 'FeatureCollection'
+ };
+ reader.read(ellipseObj).then((features) => {
+ expect(features.length).toEqual(2);
+ expect(features[1] instanceof geo.markerFeature).toBe(true);
+ map.draw();
+ destroyMap();
+ restoreWebglRenderer();
+ done();
+ }).catch((err) => {
+ destroyMap();
+ restoreWebglRenderer();
+ throw Error(err);
+ });
+ });
});

Add support for ellipses in the geojson parser.

Specifically, if a polygon has been tagged as a ellipse or circle,
render it so.