geojs 1.8.4 → 1.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/geo.js +1316 -1084
- package/geo.lean.js +1316 -1084
- package/geo.lean.min.js +2 -2
- package/geo.min.js +2 -2
- package/package.json +1 -1
- package/src/{annotation.js → annotation/annotation.js} +38 -1374
- package/src/annotation/circleAnnotation.js +33 -0
- package/src/annotation/ellipseAnnotation.js +80 -0
- package/src/annotation/index.js +21 -0
- package/src/annotation/lineAnnotation.js +344 -0
- package/src/annotation/pointAnnotation.js +208 -0
- package/src/annotation/polygonAnnotation.js +313 -0
- package/src/annotation/rectangleAnnotation.js +402 -0
- package/src/annotation/squareAnnotation.js +33 -0
- package/src/fetchQueue.js +25 -11
- package/src/index.js +1 -1
- package/src/transform.js +15 -0
- package/src/typedef.js +9 -0
- package/src/webgl/lineFeature.js +7 -3
- package/src/webgl/polygonFeature.js +11 -8
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
const $ = require('jquery');
|
|
2
|
+
const inherit = require('../inherit');
|
|
3
|
+
const registerAnnotation = require('../registry').registerAnnotation;
|
|
4
|
+
const lineFeature = require('../lineFeature');
|
|
5
|
+
const polygonFeature = require('../polygonFeature');
|
|
6
|
+
|
|
7
|
+
const annotation = require('./annotation').annotation;
|
|
8
|
+
const annotationState = require('./annotation').state;
|
|
9
|
+
const continuousVerticesActions = require('./annotation').continuousVerticesActions;
|
|
10
|
+
const continuousVerticesProcessAction = require('./annotation').continuousVerticesProcessAction;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Polygon annotation specification. Extends {@link geo.annotation.spec}.
|
|
14
|
+
*
|
|
15
|
+
* @typedef {object} geo.polygonAnnotation.spec
|
|
16
|
+
* @extends geo.annotation.spec
|
|
17
|
+
* @property {geo.geoPosition[]} [vertices] A list of vertices in map gcs
|
|
18
|
+
* coordinates. These must be in order around the perimeter of the polygon
|
|
19
|
+
* (in either direction).
|
|
20
|
+
* @property {geo.geoPosition[]} [coordinates] An alternate name for
|
|
21
|
+
* `vertices`.
|
|
22
|
+
* @property {geo.polygonFeature.styleSpec} [style] The style to apply to a
|
|
23
|
+
* finished polygon. This uses styles for {@link geo.polygonFeature}.
|
|
24
|
+
* @property {geo.polygonFeature.styleSpec} [editStyle] The style to apply to ai
|
|
25
|
+
* polygon in edit mode.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Polygon annotation class
|
|
30
|
+
*
|
|
31
|
+
* When complete, polygons are rendered as polygons. During creation they are
|
|
32
|
+
* rendered as lines and polygons.
|
|
33
|
+
*
|
|
34
|
+
* @class
|
|
35
|
+
* @alias geo.polygonAnnotation
|
|
36
|
+
* @extends geo.annotation
|
|
37
|
+
*
|
|
38
|
+
* @param {geo.polygonAnnotation.spec?} [args] Options for the annotation.
|
|
39
|
+
*/
|
|
40
|
+
var polygonAnnotation = function (args) {
|
|
41
|
+
'use strict';
|
|
42
|
+
if (!(this instanceof polygonAnnotation)) {
|
|
43
|
+
return new polygonAnnotation(args);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
args = $.extend(true, {}, {
|
|
47
|
+
style: {
|
|
48
|
+
fill: true,
|
|
49
|
+
fillColor: {r: 0, g: 1, b: 0},
|
|
50
|
+
fillOpacity: 0.25,
|
|
51
|
+
polygon: function (d) { return d.polygon; },
|
|
52
|
+
stroke: true,
|
|
53
|
+
strokeColor: {r: 0, g: 0, b: 0},
|
|
54
|
+
strokeOpacity: 1,
|
|
55
|
+
strokeWidth: 3,
|
|
56
|
+
uniformPolygon: true
|
|
57
|
+
},
|
|
58
|
+
highlightStyle: {
|
|
59
|
+
fillColor: {r: 0, g: 1, b: 1},
|
|
60
|
+
fillOpacity: 0.5,
|
|
61
|
+
strokeWidth: 5
|
|
62
|
+
},
|
|
63
|
+
createStyle: {
|
|
64
|
+
closed: false,
|
|
65
|
+
fillColor: {r: 0.3, g: 0.3, b: 0.3},
|
|
66
|
+
fillOpacity: 0.25,
|
|
67
|
+
line: function (d) {
|
|
68
|
+
const coord = m_this._coordinates();
|
|
69
|
+
/* Return an array that has the same number of items as we have
|
|
70
|
+
* vertices. */
|
|
71
|
+
return Array.apply(null, Array((coord.outer || coord).length)).map(
|
|
72
|
+
function () { return d; });
|
|
73
|
+
},
|
|
74
|
+
position: function (d, i) {
|
|
75
|
+
if (d.x !== undefined) {
|
|
76
|
+
return d.x;
|
|
77
|
+
}
|
|
78
|
+
return m_this.options('vertices')[i];
|
|
79
|
+
},
|
|
80
|
+
stroke: false,
|
|
81
|
+
strokeColor: {r: 0, g: 0, b: 1}
|
|
82
|
+
}
|
|
83
|
+
}, args || {});
|
|
84
|
+
args.vertices = args.vertices || args.coordinates || [];
|
|
85
|
+
delete args.coordinates;
|
|
86
|
+
annotation.call(this, 'polygon', args);
|
|
87
|
+
|
|
88
|
+
var m_this = this,
|
|
89
|
+
s_actions = this.actions;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get a list of renderable features for this annotation. When the polygon
|
|
93
|
+
* is done, this is just a single polygon. During creation this can be a
|
|
94
|
+
* polygon and line at z-levels 1 and 2.
|
|
95
|
+
*
|
|
96
|
+
* @returns {array} An array of features.
|
|
97
|
+
*/
|
|
98
|
+
this.features = function () {
|
|
99
|
+
var opt = m_this.options(),
|
|
100
|
+
state = m_this.state(),
|
|
101
|
+
style = m_this.styleForState(state),
|
|
102
|
+
features;
|
|
103
|
+
switch (state) {
|
|
104
|
+
case annotationState.create:
|
|
105
|
+
features = [];
|
|
106
|
+
if (opt.vertices && (opt.vertices.outer || opt.vertices.length >= 3)) {
|
|
107
|
+
features[1] = {
|
|
108
|
+
polygon: {
|
|
109
|
+
polygon: opt.vertices,
|
|
110
|
+
style: style
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (opt.vertices && opt.vertices.length >= 2) {
|
|
115
|
+
features[2] = {
|
|
116
|
+
line: {
|
|
117
|
+
line: opt.vertices,
|
|
118
|
+
style: style
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
default:
|
|
124
|
+
features = [{
|
|
125
|
+
polygon: {
|
|
126
|
+
polygon: opt.vertices,
|
|
127
|
+
style: style
|
|
128
|
+
}
|
|
129
|
+
}];
|
|
130
|
+
if (state === annotationState.edit) {
|
|
131
|
+
m_this._addEditHandles(features, opt.vertices);
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
return features;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get and optionally set coordinates associated with this annotation in the
|
|
140
|
+
* map gcs coordinate system.
|
|
141
|
+
*
|
|
142
|
+
* @param {geo.geoPosition[]} [coordinates] An optional array of coordinates
|
|
143
|
+
* to set.
|
|
144
|
+
* @returns {geo.geoPosition[]} The current array of coordinates.
|
|
145
|
+
*/
|
|
146
|
+
this._coordinates = function (coordinates) {
|
|
147
|
+
if (coordinates) {
|
|
148
|
+
m_this.options('vertices', coordinates);
|
|
149
|
+
}
|
|
150
|
+
return m_this.options('vertices');
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Handle a mouse move on this annotation.
|
|
155
|
+
*
|
|
156
|
+
* @param {geo.event} evt The mouse move event.
|
|
157
|
+
* @returns {boolean} Truthy to update the annotation, falsy to not
|
|
158
|
+
* update anything.
|
|
159
|
+
*/
|
|
160
|
+
this.mouseMove = function (evt) {
|
|
161
|
+
if (m_this.state() !== annotationState.create) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
var vertices = m_this.options('vertices');
|
|
165
|
+
if (vertices.length) {
|
|
166
|
+
vertices[vertices.length - 1] = evt.mapgcs;
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Handle a mouse click on this annotation. If the event is processed,
|
|
173
|
+
* evt.handled should be set to `true` to prevent further processing.
|
|
174
|
+
*
|
|
175
|
+
* @param {geo.event} evt The mouse click event.
|
|
176
|
+
* @returns {boolean|string} `true` to update the annotation, `'done'` if
|
|
177
|
+
* the annotation was completed (changed from create to done state),
|
|
178
|
+
* `'remove'` if the annotation should be removed, falsy to not update
|
|
179
|
+
* anything.
|
|
180
|
+
*/
|
|
181
|
+
this.mouseClick = function (evt) {
|
|
182
|
+
var layer = m_this.layer();
|
|
183
|
+
if (m_this.state() !== annotationState.create || !layer) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
var end = !!evt.buttonsDown.right, skip;
|
|
187
|
+
if (!evt.buttonsDown.left && !evt.buttonsDown.right) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
var vertices = m_this.options('vertices');
|
|
191
|
+
if (evt.buttonsDown.right && !vertices.length) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
evt.handled = true;
|
|
195
|
+
if (evt.buttonsDown.left) {
|
|
196
|
+
if (vertices.length) {
|
|
197
|
+
if (vertices.length >= 2 && layer.displayDistance(
|
|
198
|
+
vertices[vertices.length - 2], null, evt.map, 'display') <=
|
|
199
|
+
layer.options('adjacentPointProximity')) {
|
|
200
|
+
skip = true;
|
|
201
|
+
if (m_this._lastClick &&
|
|
202
|
+
evt.time - m_this._lastClick < layer.options('dblClickTime')) {
|
|
203
|
+
end = true;
|
|
204
|
+
}
|
|
205
|
+
} else if (vertices.length >= 2 && layer.displayDistance(
|
|
206
|
+
vertices[0], null, evt.map, 'display') <=
|
|
207
|
+
layer.options('finalPointProximity')) {
|
|
208
|
+
end = true;
|
|
209
|
+
} else {
|
|
210
|
+
vertices[vertices.length - 1] = evt.mapgcs;
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
vertices.push(evt.mapgcs);
|
|
214
|
+
}
|
|
215
|
+
if (!end && !skip) {
|
|
216
|
+
vertices.push(evt.mapgcs);
|
|
217
|
+
}
|
|
218
|
+
m_this._lastClick = evt.time;
|
|
219
|
+
m_this._lastClickVertexCount = vertices.length;
|
|
220
|
+
}
|
|
221
|
+
if (end) {
|
|
222
|
+
if (vertices.length < 4) {
|
|
223
|
+
return 'remove';
|
|
224
|
+
}
|
|
225
|
+
vertices.pop();
|
|
226
|
+
m_this.state(annotationState.done);
|
|
227
|
+
return 'done';
|
|
228
|
+
}
|
|
229
|
+
return !skip;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Return actions needed for the specified state of this annotation.
|
|
234
|
+
*
|
|
235
|
+
* @param {string} [state] The state to return actions for. Defaults to
|
|
236
|
+
* the current state.
|
|
237
|
+
* @returns {geo.actionRecord[]} A list of actions.
|
|
238
|
+
*/
|
|
239
|
+
this.actions = function (state) {
|
|
240
|
+
return continuousVerticesActions(m_this, s_actions, state, 'polygon', arguments);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Process any actions for this annotation.
|
|
245
|
+
*
|
|
246
|
+
* @param {geo.event} evt The action event.
|
|
247
|
+
* @returns {boolean|string} `true` to update the annotation, `'done'` if the
|
|
248
|
+
* annotation was completed (changed from create to done state),
|
|
249
|
+
* `'remove'` if the annotation should be removed, falsy to not update
|
|
250
|
+
* anything.
|
|
251
|
+
*/
|
|
252
|
+
this.processAction = function (evt) {
|
|
253
|
+
return continuousVerticesProcessAction(m_this, evt, 'polygon');
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Return the coordinates to be stored in a geojson geometry object.
|
|
258
|
+
*
|
|
259
|
+
* @param {string|geo.transform|null} [gcs] `undefined` to use the interface
|
|
260
|
+
* gcs, `null` to use the map gcs, or any other transform.
|
|
261
|
+
* @returns {array} An array of flattened coordinates in the interface gcs
|
|
262
|
+
* coordinate system. `undefined` if this annotation is incomplete.
|
|
263
|
+
*/
|
|
264
|
+
this._geojsonCoordinates = function (gcs) {
|
|
265
|
+
var src = m_this.coordinates(gcs);
|
|
266
|
+
if (!src || (!src.outer && src.length < 3) || m_this.state() === annotationState.create) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
var coord = [];
|
|
270
|
+
if (!src.outer) {
|
|
271
|
+
coord = [src.map((pt) => [pt.x, pt.y])];
|
|
272
|
+
coord[0].push(coord[0][0].slice());
|
|
273
|
+
} else {
|
|
274
|
+
coord = [src.outer.map((pt) => [pt.x, pt.y])];
|
|
275
|
+
coord[0].push(coord[0][0].slice());
|
|
276
|
+
(src.inner || []).forEach((h) => {
|
|
277
|
+
const poly = h.map((pt) => [pt.x, pt.y]);
|
|
278
|
+
poly.push(poly[0].slice());
|
|
279
|
+
coord.push(poly);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
return coord;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Return the geometry type that is used to store this annotation in geojson.
|
|
287
|
+
*
|
|
288
|
+
* @returns {string} A geojson geometry type.
|
|
289
|
+
*/
|
|
290
|
+
this._geojsonGeometryType = function () {
|
|
291
|
+
return 'Polygon';
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Return a list of styles that should be preserved in a geojson
|
|
296
|
+
* representation of the annotation.
|
|
297
|
+
*
|
|
298
|
+
* @returns {string[]} A list of style names to store.
|
|
299
|
+
*/
|
|
300
|
+
this._geojsonStyles = function () {
|
|
301
|
+
return [
|
|
302
|
+
'fill', 'fillColor', 'fillOpacity', 'lineCap', 'lineJoin', 'stroke',
|
|
303
|
+
'strokeColor', 'strokeOffset', 'strokeOpacity', 'strokeWidth'];
|
|
304
|
+
};
|
|
305
|
+
};
|
|
306
|
+
inherit(polygonAnnotation, annotation);
|
|
307
|
+
|
|
308
|
+
var polygonRequiredFeatures = {};
|
|
309
|
+
polygonRequiredFeatures[polygonFeature.capabilities.feature] = true;
|
|
310
|
+
polygonRequiredFeatures[lineFeature.capabilities.basic] = [annotationState.create];
|
|
311
|
+
registerAnnotation('polygon', polygonAnnotation, polygonRequiredFeatures);
|
|
312
|
+
|
|
313
|
+
module.exports = polygonAnnotation;
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
const $ = require('jquery');
|
|
2
|
+
const inherit = require('../inherit');
|
|
3
|
+
const geo_event = require('../event');
|
|
4
|
+
const geo_action = require('../action');
|
|
5
|
+
const registerAnnotation = require('../registry').registerAnnotation;
|
|
6
|
+
const polygonFeature = require('../polygonFeature');
|
|
7
|
+
|
|
8
|
+
const annotation = require('./annotation').annotation;
|
|
9
|
+
const annotationState = require('./annotation').state;
|
|
10
|
+
const annotationActionOwner = require('./annotation').annotationActionOwner;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Rectangle annotation specification. Extends {@link geo.annotation.spec}.
|
|
14
|
+
*
|
|
15
|
+
* @typedef {object} geo.rectangleAnnotation.spec
|
|
16
|
+
* @extends geo.annotation.spec
|
|
17
|
+
* @property {geo.geoPosition[]} [corners] A list of four corners in map gcs
|
|
18
|
+
* coordinates. These must be in order around the perimeter of the
|
|
19
|
+
* rectangle (in either direction).
|
|
20
|
+
* @property {geo.geoPosition[]} [coordinates] An alternate name for `corners`.
|
|
21
|
+
* @property {geo.polygonFeature.styleSpec} [style] The style to apply to a
|
|
22
|
+
* finished rectangle. This uses styles for {@link geo.polygonFeature}.
|
|
23
|
+
* @property {geo.polygonFeature.styleSpec} [editStyle] The style to apply to a
|
|
24
|
+
* rectangle in edit mode.
|
|
25
|
+
* @property {number|number[]|function} [constraint] If specified, an aspect
|
|
26
|
+
* ratio or list of aspect ratios to constraint the rectangle to. If a
|
|
27
|
+
* function, a selection constraint function to call to adjust the
|
|
28
|
+
* rectangle.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Rectangle annotation class.
|
|
33
|
+
*
|
|
34
|
+
* Rectangles are always rendered as polygons. This could be changed -- if no
|
|
35
|
+
* stroke is specified, the quad feature would be sufficient and work on more
|
|
36
|
+
* renderers.
|
|
37
|
+
*
|
|
38
|
+
* @class
|
|
39
|
+
* @alias geo.rectangleAnnotation
|
|
40
|
+
* @extends geo.annotation
|
|
41
|
+
*
|
|
42
|
+
* @param {geo.rectangleAnnotation.spec?} [args] Options for the annotation.
|
|
43
|
+
* @param {string} [annotationName='rectangle'] Override the annotation name.
|
|
44
|
+
*/
|
|
45
|
+
var rectangleAnnotation = function (args, annotationName) {
|
|
46
|
+
'use strict';
|
|
47
|
+
if (!(this instanceof rectangleAnnotation)) {
|
|
48
|
+
return new rectangleAnnotation(args, annotationName);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
args = $.extend(true, {}, {
|
|
52
|
+
style: {
|
|
53
|
+
fill: true,
|
|
54
|
+
fillColor: {r: 0, g: 1, b: 0},
|
|
55
|
+
fillOpacity: 0.25,
|
|
56
|
+
polygon: function (d) { return d.polygon; },
|
|
57
|
+
stroke: true,
|
|
58
|
+
strokeColor: {r: 0, g: 0, b: 0},
|
|
59
|
+
strokeOpacity: 1,
|
|
60
|
+
strokeWidth: 3,
|
|
61
|
+
uniformPolygon: true
|
|
62
|
+
},
|
|
63
|
+
highlightStyle: {
|
|
64
|
+
fillColor: {r: 0, g: 1, b: 1},
|
|
65
|
+
fillOpacity: 0.5,
|
|
66
|
+
strokeWidth: 5
|
|
67
|
+
},
|
|
68
|
+
createStyle: {
|
|
69
|
+
fillColor: {r: 0.3, g: 0.3, b: 0.3},
|
|
70
|
+
fillOpacity: 0.25,
|
|
71
|
+
strokeColor: {r: 0, g: 0, b: 1}
|
|
72
|
+
}
|
|
73
|
+
}, args || {});
|
|
74
|
+
args.corners = args.corners || args.coordinates || [];
|
|
75
|
+
delete args.coordinates;
|
|
76
|
+
annotation.call(this, annotationName || 'rectangle', args);
|
|
77
|
+
|
|
78
|
+
var m_this = this,
|
|
79
|
+
s_actions = this.actions,
|
|
80
|
+
s_processEditAction = this.processEditAction;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Return actions needed for the specified state of this annotation.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} [state] The state to return actions for. Defaults to
|
|
86
|
+
* the current state.
|
|
87
|
+
* @returns {geo.actionRecord[]} A list of actions.
|
|
88
|
+
*/
|
|
89
|
+
this.actions = function (state) {
|
|
90
|
+
if (!state) {
|
|
91
|
+
state = m_this.state();
|
|
92
|
+
}
|
|
93
|
+
switch (state) {
|
|
94
|
+
case annotationState.create:
|
|
95
|
+
return [{
|
|
96
|
+
action: geo_action.annotation_rectangle,
|
|
97
|
+
name: 'rectangle create',
|
|
98
|
+
owner: annotationActionOwner,
|
|
99
|
+
input: 'left',
|
|
100
|
+
modifiers: {shift: false, ctrl: false},
|
|
101
|
+
selectionRectangle: true,
|
|
102
|
+
selectionConstraint: this._selectionConstraint
|
|
103
|
+
}];
|
|
104
|
+
default:
|
|
105
|
+
return s_actions.apply(m_this, arguments);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Process any actions for this annotation.
|
|
111
|
+
*
|
|
112
|
+
* @param {geo.event} evt The action event.
|
|
113
|
+
* @returns {boolean|string} `true` to update the annotation, `'done'` if the
|
|
114
|
+
* annotation was completed (changed from create to done state),
|
|
115
|
+
* `'remove'` if the annotation should be removed, falsy to not update
|
|
116
|
+
* anything.
|
|
117
|
+
*/
|
|
118
|
+
this.processAction = function (evt) {
|
|
119
|
+
var layer = m_this.layer();
|
|
120
|
+
if (m_this.state() !== annotationState.create || !layer ||
|
|
121
|
+
evt.event !== geo_event.actionselection ||
|
|
122
|
+
evt.state.action !== geo_action.annotation_rectangle) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
var map = layer.map(),
|
|
126
|
+
corners = [
|
|
127
|
+
/* Keep in map gcs, not interface gcs to avoid wrapping issues */
|
|
128
|
+
map.displayToGcs({x: evt.lowerLeft.x, y: evt.lowerLeft.y}, null),
|
|
129
|
+
map.displayToGcs({x: evt.lowerLeft.x, y: evt.upperRight.y}, null),
|
|
130
|
+
map.displayToGcs({x: evt.upperRight.x, y: evt.upperRight.y}, null),
|
|
131
|
+
map.displayToGcs({x: evt.upperRight.x, y: evt.lowerLeft.y}, null)
|
|
132
|
+
];
|
|
133
|
+
if (this._selectionConstraint && evt.mouse && evt.state.origin) {
|
|
134
|
+
this._selectionConstraint(evt.mouse.mapgcs, evt.state.origin.mapgcs, corners);
|
|
135
|
+
}
|
|
136
|
+
/* Don't keep rectangles that have nearly zero area in display pixels */
|
|
137
|
+
if (layer.displayDistance(corners[0], null, corners[1], null) *
|
|
138
|
+
layer.displayDistance(corners[0], null, corners[3], null) < 0.01) {
|
|
139
|
+
return 'remove';
|
|
140
|
+
}
|
|
141
|
+
m_this.options('corners', corners);
|
|
142
|
+
m_this.state(annotationState.done);
|
|
143
|
+
return 'done';
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get a list of renderable features for this annotation.
|
|
148
|
+
*
|
|
149
|
+
* @returns {array} An array of features.
|
|
150
|
+
*/
|
|
151
|
+
this.features = function () {
|
|
152
|
+
var opt = m_this.options(),
|
|
153
|
+
state = m_this.state(),
|
|
154
|
+
features;
|
|
155
|
+
features = [];
|
|
156
|
+
if (opt.corners && opt.corners.length >= 4) {
|
|
157
|
+
features = [{
|
|
158
|
+
polygon: {
|
|
159
|
+
polygon: opt.corners,
|
|
160
|
+
style: m_this.styleForState(state)
|
|
161
|
+
}
|
|
162
|
+
}];
|
|
163
|
+
}
|
|
164
|
+
if (state === annotationState.edit) {
|
|
165
|
+
m_this._addEditHandles(features, opt.corners);
|
|
166
|
+
}
|
|
167
|
+
return features;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get and optionally set coordinates associated with this annotation in the
|
|
172
|
+
* map gcs coordinate system.
|
|
173
|
+
*
|
|
174
|
+
* @param {geo.geoPosition[]} [coordinates] An optional array of coordinates
|
|
175
|
+
* to set.
|
|
176
|
+
* @returns {geo.geoPosition[]} The current array of coordinates.
|
|
177
|
+
*/
|
|
178
|
+
this._coordinates = function (coordinates) {
|
|
179
|
+
if (coordinates && coordinates.length >= 4) {
|
|
180
|
+
m_this.options('corners', coordinates.slice(0, 4));
|
|
181
|
+
/* Should we ensure that the four points form a rectangle in the current
|
|
182
|
+
* projection, though this might not be rectangular in another gcs? */
|
|
183
|
+
}
|
|
184
|
+
return m_this.options('corners');
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
this._coordinateOption = 'corners';
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Return the coordinates to be stored in a geojson geometry object.
|
|
191
|
+
*
|
|
192
|
+
* @param {string|geo.transform|null} [gcs] `undefined` to use the interface
|
|
193
|
+
* gcs, `null` to use the map gcs, or any other transform.
|
|
194
|
+
* @returns {array} An array of flattened coordinates in the interface gcs
|
|
195
|
+
* coordinate system. `undefined` if this annotation is incomplete.
|
|
196
|
+
*/
|
|
197
|
+
this._geojsonCoordinates = function (gcs) {
|
|
198
|
+
var src = m_this.coordinates(gcs);
|
|
199
|
+
if (!src || m_this.state() === annotationState.create || src.length < 4) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
var coord = [];
|
|
203
|
+
for (var i = 0; i < 4; i += 1) {
|
|
204
|
+
coord.push([src[i].x, src[i].y]);
|
|
205
|
+
}
|
|
206
|
+
coord.push([src[0].x, src[0].y]);
|
|
207
|
+
return [coord];
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Return the geometry type that is used to store this annotation in geojson.
|
|
212
|
+
*
|
|
213
|
+
* @returns {string} A geojson geometry type.
|
|
214
|
+
*/
|
|
215
|
+
this._geojsonGeometryType = function () {
|
|
216
|
+
return 'Polygon';
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Return a list of styles that should be preserved in a geojson
|
|
221
|
+
* representation of the annotation.
|
|
222
|
+
*
|
|
223
|
+
* @returns {string[]} A list of style names to store.
|
|
224
|
+
*/
|
|
225
|
+
this._geojsonStyles = function () {
|
|
226
|
+
return [
|
|
227
|
+
'fill', 'fillColor', 'fillOpacity', 'lineCap', 'lineJoin', 'stroke',
|
|
228
|
+
'strokeColor', 'strokeOffset', 'strokeOpacity', 'strokeWidth'];
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Set three corners based on an initial corner and a mouse event.
|
|
233
|
+
*
|
|
234
|
+
* @param {geo.geoPosition} corners An array of four corners to update.
|
|
235
|
+
* @param {geo.event} evt The mouse move event.
|
|
236
|
+
*/
|
|
237
|
+
this._setCornersFromMouse = function (corners, evt) {
|
|
238
|
+
var map = m_this.layer().map(),
|
|
239
|
+
c0 = map.gcsToDisplay({x: corners[0].x, y: corners[0].y}, null),
|
|
240
|
+
c2 = map.gcsToDisplay(evt.mapgcs, null),
|
|
241
|
+
c1 = {x: c2.x, y: c0.y},
|
|
242
|
+
c3 = {x: c0.x, y: c2.y};
|
|
243
|
+
corners[2] = $.extend({}, evt.mapgcs);
|
|
244
|
+
corners[1] = map.displayToGcs(c1, null);
|
|
245
|
+
corners[3] = map.displayToGcs(c3, null);
|
|
246
|
+
if (this._selectionConstraint) {
|
|
247
|
+
this._selectionConstraint(evt.mapgcs, corners[0], corners);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Handle a mouse move on this annotation.
|
|
253
|
+
*
|
|
254
|
+
* @param {geo.event} evt The mouse move event.
|
|
255
|
+
* @returns {boolean} Truthy to update the annotation, falsy to not
|
|
256
|
+
* update anything.
|
|
257
|
+
*/
|
|
258
|
+
this.mouseMove = function (evt) {
|
|
259
|
+
if (m_this.state() !== annotationState.create) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
var corners = m_this.options('corners');
|
|
263
|
+
if (corners.length) {
|
|
264
|
+
m_this._setCornersFromMouse(corners, evt);
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Handle a mouse click on this annotation. If the event is processed,
|
|
271
|
+
* evt.handled should be set to `true` to prevent further processing.
|
|
272
|
+
*
|
|
273
|
+
* @param {geo.event} evt The mouse click event.
|
|
274
|
+
* @returns {boolean|string} `true` to update the annotation, `'done'` if
|
|
275
|
+
* the annotation was completed (changed from create to done state),
|
|
276
|
+
* `'remove'` if the annotation should be removed, falsy to not update
|
|
277
|
+
* anything.
|
|
278
|
+
*/
|
|
279
|
+
this.mouseClick = function (evt) {
|
|
280
|
+
var layer = m_this.layer();
|
|
281
|
+
if (m_this.state() !== annotationState.create || !layer) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (!evt.buttonsDown.left && !evt.buttonsDown.right) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
var corners = m_this.options('corners');
|
|
288
|
+
if (evt.buttonsDown.right && !corners.length) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
evt.handled = true;
|
|
292
|
+
if (corners.length) {
|
|
293
|
+
m_this._setCornersFromMouse(corners, evt);
|
|
294
|
+
/* Don't keep rectangles that have nearly zero area in display pixels */
|
|
295
|
+
if (layer.displayDistance(corners[0], null, corners[1], null) *
|
|
296
|
+
layer.displayDistance(corners[0], null, corners[3], null) < 0.01) {
|
|
297
|
+
return 'remove';
|
|
298
|
+
}
|
|
299
|
+
m_this.state(annotationState.done);
|
|
300
|
+
return 'done';
|
|
301
|
+
}
|
|
302
|
+
if (evt.buttonsDown.left) {
|
|
303
|
+
corners.push($.extend({}, evt.mapgcs));
|
|
304
|
+
corners.push($.extend({}, evt.mapgcs));
|
|
305
|
+
corners.push($.extend({}, evt.mapgcs));
|
|
306
|
+
corners.push($.extend({}, evt.mapgcs));
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Process any edit actions for this annotation.
|
|
313
|
+
*
|
|
314
|
+
* @param {geo.event} evt The action event.
|
|
315
|
+
* @returns {boolean|string} `true` to update the annotation, falsy to not
|
|
316
|
+
* update anything.
|
|
317
|
+
*/
|
|
318
|
+
this.processEditAction = function (evt) {
|
|
319
|
+
var start = m_this._editHandle.startCoordinates,
|
|
320
|
+
delta = {
|
|
321
|
+
x: evt.mouse.mapgcs.x - evt.state.origin.mapgcs.x,
|
|
322
|
+
y: evt.mouse.mapgcs.y - evt.state.origin.mapgcs.y
|
|
323
|
+
},
|
|
324
|
+
type = m_this._editHandle.handle.type,
|
|
325
|
+
index = m_this._editHandle.handle.index,
|
|
326
|
+
ang = [
|
|
327
|
+
Math.atan2(start[1].y - start[0].y, start[1].x - start[0].x),
|
|
328
|
+
Math.atan2(start[2].y - start[1].y, start[2].x - start[1].x),
|
|
329
|
+
Math.atan2(start[3].y - start[2].y, start[3].x - start[2].x),
|
|
330
|
+
Math.atan2(start[0].y - start[3].y, start[0].x - start[3].x)
|
|
331
|
+
],
|
|
332
|
+
corners, delta1, delta2, ang1, ang2;
|
|
333
|
+
// an angle can be zero because it is horizontal or undefined. If opposite
|
|
334
|
+
// angles are both zero, this is a degenerate rectangle (a line or a point)
|
|
335
|
+
if (!ang[0] && !ang[1] && !ang[2] && !ang[3]) {
|
|
336
|
+
ang[1] = Math.PI / 2;
|
|
337
|
+
ang[2] = Math.PI;
|
|
338
|
+
ang[3] = -Math.PI / 2;
|
|
339
|
+
}
|
|
340
|
+
if (!ang[0] && !ang[2]) {
|
|
341
|
+
ang[0] = ang[1] - Math.PI / 2;
|
|
342
|
+
ang[2] = ang[1] + Math.PI / 2;
|
|
343
|
+
}
|
|
344
|
+
if (!ang[1] && !ang[3]) {
|
|
345
|
+
ang[1] = ang[2] - Math.PI / 2;
|
|
346
|
+
ang[3] = ang[2] + Math.PI / 2;
|
|
347
|
+
}
|
|
348
|
+
switch (type) {
|
|
349
|
+
case 'vertex':
|
|
350
|
+
corners = start.map(function (elem) {
|
|
351
|
+
return {x: elem.x, y: elem.y};
|
|
352
|
+
});
|
|
353
|
+
ang1 = ang[(index + 1) % 4];
|
|
354
|
+
delta1 = {
|
|
355
|
+
x: (delta.x * Math.cos(ang1) + delta.y * Math.sin(ang1)) * Math.cos(ang1),
|
|
356
|
+
y: (delta.y * Math.sin(ang1) + delta.x * Math.cos(ang1)) * Math.sin(ang1)
|
|
357
|
+
};
|
|
358
|
+
ang2 = ang[index];
|
|
359
|
+
delta2 = {
|
|
360
|
+
x: (delta.x * Math.cos(ang2) + delta.y * Math.sin(ang2)) * Math.cos(ang2),
|
|
361
|
+
y: (delta.y * Math.sin(ang2) + delta.x * Math.cos(ang2)) * Math.sin(ang2)
|
|
362
|
+
};
|
|
363
|
+
corners[index].x += delta.x;
|
|
364
|
+
corners[index].y += delta.y;
|
|
365
|
+
corners[(index + 1) % 4].x += delta1.x;
|
|
366
|
+
corners[(index + 1) % 4].y += delta1.y;
|
|
367
|
+
corners[(index + 3) % 4].x += delta2.x;
|
|
368
|
+
corners[(index + 3) % 4].y += delta2.y;
|
|
369
|
+
if (this._selectionConstraint) {
|
|
370
|
+
corners = this._selectionConstraint(evt.mouse.mapgcs, evt.state.origin.mapgcs, corners, 'vertex', ang, index).corners;
|
|
371
|
+
}
|
|
372
|
+
m_this.options('corners', corners);
|
|
373
|
+
return true;
|
|
374
|
+
case 'edge':
|
|
375
|
+
corners = start.map(function (elem) {
|
|
376
|
+
return {x: elem.x, y: elem.y};
|
|
377
|
+
});
|
|
378
|
+
ang1 = ang[(index + 1) % 4];
|
|
379
|
+
delta = {
|
|
380
|
+
x: (delta.x * Math.cos(ang1) + delta.y * Math.sin(ang1)) * Math.cos(ang1),
|
|
381
|
+
y: (delta.y * Math.sin(ang1) + delta.x * Math.cos(ang1)) * Math.sin(ang1)
|
|
382
|
+
};
|
|
383
|
+
corners[index].x += delta.x;
|
|
384
|
+
corners[index].y += delta.y;
|
|
385
|
+
corners[(index + 1) % 4].x += delta.x;
|
|
386
|
+
corners[(index + 1) % 4].y += delta.y;
|
|
387
|
+
if (this._selectionConstraint) {
|
|
388
|
+
corners = this._selectionConstraint(evt.mouse.mapgcs, evt.state.origin.mapgcs, corners, 'edge', ang, index).corners;
|
|
389
|
+
}
|
|
390
|
+
m_this.options('corners', corners);
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
return s_processEditAction.apply(m_this, arguments);
|
|
394
|
+
};
|
|
395
|
+
};
|
|
396
|
+
inherit(rectangleAnnotation, annotation);
|
|
397
|
+
|
|
398
|
+
var rectangleRequiredFeatures = {};
|
|
399
|
+
rectangleRequiredFeatures[polygonFeature.capabilities.feature] = true;
|
|
400
|
+
registerAnnotation('rectangle', rectangleAnnotation, rectangleRequiredFeatures);
|
|
401
|
+
|
|
402
|
+
module.exports = rectangleAnnotation;
|