Skip to content

Commit 2281936

Browse files
authored
Merge pull request #354 from geoblocks/drawing_mask
Added drawing extent mask
2 parents df30a14 + 9190977 commit 2281936

File tree

6 files changed

+121
-7
lines changed

6 files changed

+121
-7
lines changed

demos/schm/demo.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ const densifier = new UnsnappedDensifier({ });
5555
return doubleClick(mapBrowserEvent) && pointType !== 'POI';
5656
};
5757

58+
const search = new URLSearchParams(document.location.search);
59+
60+
const showMask = search.get('drawMask') === 'true';
61+
5862
const trackManager = new TrackManager({
5963
map: map,
6064
router: router,
@@ -68,9 +72,10 @@ const densifier = new UnsnappedDensifier({ });
6872
addLastPointCondition: singleClick, // we have to use single click otherwise the double click is not fired
6973
addControlPointCondition: doubleClick,
7074
hitTolerance: 15,
75+
drawExtent: showMask ? [2443048, 1030343, 2894802, 1339995] : undefined,
76+
drawMaskColor: showMask ? 'rgba(255, 0, 0, 0.3)' : undefined,
7177
});
7278

73-
const search = new URLSearchParams(document.location.search);
7479
const trackId = search.get('trackId');
7580
if (trackId) {
7681
trackManager.restoreFeatures([
@@ -83,6 +88,9 @@ const densifier = new UnsnappedDensifier({ });
8388
}
8489

8590
trackManager.mode = 'edit';
91+
document.getElementById('edit-mode-switch').addEventListener('click', () => {
92+
trackManager.mode = trackManager.mode === 'edit' ? '' : 'edit';
93+
})
8694
}
8795

8896
main();

demos/schm/schm.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,16 @@
3030
-webkit-user-select: none;
3131
-moz-user-select: none;
3232
}
33+
#edit-mode-switch {
34+
position: absolute;
35+
top: 10px;
36+
right: 10px;
37+
}
3338
</style>
3439
</head>
3540

3641
<body>
3742
<div id="map"></div>
43+
<button id="edit-mode-switch">Switch edit mode</button>
3844
</body>
3945
</html>

demos/schm/swisstopo.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export function createMap(target) {
3737
extent: extent,
3838
center: [2532661.0, 1151654.0],
3939
zoom: 10,
40+
constrainOnlyCenter: true,
4041
});
4142

4243
const bgLayer = createSwisstopoLayer('ch.swisstopo.pixelkarte-farbe');

src/interaction/TrackInteraction.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import type {FlatStyleLike} from 'ol/style/flat';
1414
import type {Pixel} from 'ol/pixel';
1515
import type {FeatureType} from './TrackData';
1616
import {Point} from 'ol/geom';
17+
import {containsCoordinate} from 'ol/extent.js';
18+
import {Extent} from "ol/extent";
19+
import {Coordinate} from "ol/coordinate.js";
1720

1821
export interface Options {
1922
map: Map;
@@ -38,12 +41,18 @@ export interface Options {
3841
* Pixel tolerance for considering the pointer close enough to a segment for snapping.
3942
*/
4043
hitTolerance: number;
44+
45+
/**
46+
* Drawing outside extent will not be possible
47+
*/
48+
drawExtent?: Extent;
4149
}
4250

4351

4452
export default class TrackInteraction extends Interaction {
4553

4654
private trackLayer_: VectorLayer<VectorSource>;
55+
private drawExtent_?: Extent;
4756
pointerOutListener?: () => void;
4857
pointerOverListener?: () => void;
4958

@@ -78,10 +87,15 @@ export default class TrackInteraction extends Interaction {
7887
});
7988
}
8089

90+
pixelAtDrawingExtent(coordinate: Coordinate): boolean {
91+
if (!this.drawExtent_?.length) return true;
92+
return containsCoordinate(this.drawExtent_, coordinate);
93+
}
94+
8195
createDrawInteraction(source: VectorSource): DrawPoint {
8296
const draw = new DrawPoint({
8397
source: source,
84-
condition: (event) => this.userAddLastPointCondition_(event) && !this.controlPointOrPOIAtPixel(event.pixel)
98+
condition: (event) => this.pixelAtDrawingExtent(event.coordinate) && this.userAddLastPointCondition_(event) && !this.controlPointOrPOIAtPixel(event.pixel)
8599
});
86100
// @ts-ignore too complicate to declare proper events
87101
draw.on('drawend', (evt) => this.dispatchEvent(evt));
@@ -93,8 +107,9 @@ export default class TrackInteraction extends Interaction {
93107
trackData: trackData,
94108
source: source,
95109
style: style,
96-
condition: (event) => !this.deleteCondition_(event),
97-
addControlPointCondition: (event) => this.userAddControlPointCondition_(event),
110+
condition: (event) => this.pixelAtDrawingExtent(event.coordinate) && !this.deleteCondition_(event),
111+
addControlPointCondition: (event) => this.pixelAtDrawingExtent(event.coordinate) && this.userAddControlPointCondition_(event),
112+
sketchPointCondition: (event) => this.pixelAtDrawingExtent(event.coordinate),
98113
hitTolerance: hitTolerance,
99114
});
100115
// @ts-ignore too complicate to declare proper events
@@ -137,6 +152,7 @@ export default class TrackInteraction extends Interaction {
137152
super();
138153

139154
this.trackLayer_ = options.trackLayer;
155+
this.drawExtent_ = options.drawExtent;
140156

141157
this.pointerOutListener = () => {
142158
this.modifyTrack_.pointAtCursorFeature.set("type", undefined);

src/interaction/TrackInteractionModify.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface Options {
3636
style: StyleLike | FlatStyleLike;
3737
condition: (mbe: MapBrowserEvent<UIEvent>) => boolean;
3838
addControlPointCondition: (mbe: MapBrowserEvent<UIEvent>) => boolean;
39+
sketchPointCondition?: (mbe: MapBrowserEvent<UIEvent>) => boolean;
3940
/**
4041
* Pixel tolerance for considering the pointer close enough to a segment for snapping.
4142
*/
@@ -54,6 +55,7 @@ export default class Modify extends PointerInteraction {
5455
private dragStarted = false;
5556
private condition_: Options['condition'];
5657
private addControlPointCondition_: Options['addControlPointCondition'];
58+
private sketchPointCondition: Options['sketchPointCondition'];
5759
private source_: Options['source'];
5860
private hitTolerance_: Options['hitTolerance'];
5961
private feature_: Feature<Point|LineString> = null;
@@ -83,7 +85,9 @@ export default class Modify extends PointerInteraction {
8385

8486
this.condition_ = options.condition;
8587

86-
this.addControlPointCondition_ = options.addControlPointCondition;
88+
this.addControlPointCondition_ = options.addControlPointCondition ? options.addControlPointCondition : () => true;
89+
90+
this.sketchPointCondition = options.sketchPointCondition;
8791

8892
this.source_ = options.source;
8993

@@ -171,9 +175,17 @@ export default class Modify extends PointerInteraction {
171175

172176

173177
handleMoveEvent(event: MapBrowserEvent<UIEvent>) {
174-
if (event.dragging) {
178+
const pointCondition = this.sketchPointCondition(event);
179+
if (event.dragging || !pointCondition) {
180+
if (!pointCondition && this.pointAtCursorFeature.get('type') === 'sketch') {
181+
this.pointAtCursorFeature.set("type", undefined);
182+
}
175183
return;
176184
}
185+
186+
if (pointCondition && this.pointAtCursorFeature.get('type') !== 'sketch') {
187+
this.pointAtCursorFeature.set("type", 'sketch');
188+
}
177189
this.pointAtCursorFeature_.getGeometry().setCoordinates(event.coordinate);
178190
this.lastPixel_ = event.pixel;
179191
this.updateSketchFeature();
@@ -206,6 +218,9 @@ export default class Modify extends PointerInteraction {
206218
}
207219

208220
handleDragEvent(event: MapBrowserEvent<UIEvent>) {
221+
if (!this.sketchPointCondition(event)) {
222+
return;
223+
}
209224
this.pointAtCursorFeature_.getGeometry().setCoordinates(event.coordinate);
210225

211226
const type = this.feature_.get('type') as FeatureType;
@@ -288,7 +303,11 @@ export default class Modify extends PointerInteraction {
288303
this.feature_ = null;
289304
return false;
290305
}
291-
this.dispatchEvent(new ModifyEvent('modifyend', this.feature_, event.coordinate));
306+
let coordinate = event.coordinate;
307+
if (!this.sketchPointCondition(event) && this.pointAtCursorFeature_) {
308+
coordinate = this.pointAtCursorFeature_.getGeometry().getCoordinates();
309+
}
310+
this.dispatchEvent(new ModifyEvent('modifyend', this.feature_, coordinate));
292311
this.dragStarted = false;
293312

294313
this.overlayFeature.setGeometry(null);

src/interaction/TrackManager.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ import type {Coordinate} from 'ol/coordinate';
2424
import type {FeatureType} from './TrackData';
2525
import type {Snapper} from 'src/snapper';
2626
import { Densifier } from 'src/densifier';
27+
import {Extent} from "ol/extent";
28+
import {EventsKey} from 'ol/events';
29+
import RenderEvent from "ol/render/Event";
30+
import {unByKey} from "ol/Observable";
2731

2832
export type TrackMode = 'edit' | '';
2933
export type TrackSubMode = 'addpoi' | 'editpoi' | '';
@@ -74,6 +78,16 @@ export interface Options {
7478
* Pixel tolerance for considering the pointer close enough to a segment for snapping.
7579
*/
7680
hitTolerance: number;
81+
82+
/**
83+
* Drawing area extent.
84+
*/
85+
drawExtent?: Extent;
86+
87+
/**
88+
* Drawing mask color. CSS string
89+
*/
90+
drawMaskColor?: string;
7791
}
7892

7993

@@ -115,6 +129,10 @@ export default class TrackManager<POIMeta> {
115129
private interaction_: TrackInteraction;
116130
private historyManager_ = new HistoryManager<Feature<Point|LineString>[]>();
117131

132+
private drawExtent_: Extent | undefined;
133+
private drawMaskColor_: string = 'rgba(241, 245, 249, 1)';
134+
private addDrawingMaskKey_: EventsKey | undefined;
135+
118136
constructor(options: Options) {
119137
this.map_ = options.map;
120138
this.source_ = options.trackLayer.getSource();
@@ -134,6 +152,11 @@ export default class TrackManager<POIMeta> {
134152
trackData: this.trackData_
135153
});
136154

155+
this.drawExtent_ = options.drawExtent;
156+
if (options.drawMaskColor) {
157+
this.drawMaskColor_ = options.drawMaskColor;
158+
}
159+
137160
this.interaction_ = new TrackInteraction({
138161
style: options.style,
139162
trackData: this.trackData_,
@@ -143,6 +166,7 @@ export default class TrackManager<POIMeta> {
143166
addLastPointCondition: options.addLastPointCondition,
144167
addControlPointCondition: options.addControlPointCondition,
145168
hitTolerance: this.hitTolerance_,
169+
drawExtent: options.drawExtent,
146170
});
147171

148172
// Hack to test profile synchro
@@ -331,6 +355,10 @@ export default class TrackManager<POIMeta> {
331355
this.map_.once("postrender", () => {
332356
this.interaction_.addMapInOutEventListeners(this.map_.getViewport());
333357
});
358+
359+
if (this.drawExtent_) {
360+
this.addDrawingMaskKey_ = this.map_.on('postcompose', (evt) => this.addDrawingMask(evt, this.drawExtent_));
361+
}
334362
} else {
335363
this.historyManager_.clear();
336364
if (this.shadowTrackLayer_) {
@@ -339,6 +367,10 @@ export default class TrackManager<POIMeta> {
339367
if (this.map_?.getViewport()) {
340368
this.interaction_.removeMapInOutEventListeners(this.map_.getViewport());
341369
}
370+
if (this.addDrawingMaskKey_) {
371+
unByKey(this.addDrawingMaskKey_);
372+
this.addDrawingMaskKey_ = undefined;
373+
}
342374
}
343375
this.interaction_.setActive(edit);
344376
this.mode_ = mode || '';
@@ -614,4 +646,36 @@ export default class TrackManager<POIMeta> {
614646
this.source_.changed();
615647
this.shadowTrackLayer_.getSource().changed();
616648
}
649+
650+
addDrawingMask(event: RenderEvent, extent: Extent) {
651+
if (!extent?.length) return;
652+
const viewport = event.target.getViewport();
653+
const canvases = viewport.getElementsByTagName('canvas');
654+
const canvas = canvases.item(canvases.length - 1);
655+
const context = canvas.getContext('2d');
656+
657+
const coordinates = [
658+
[extent[0], extent[1]], // Bottom-left
659+
[extent[0], extent[3]], // Top-left
660+
[extent[2], extent[3]], // Top-right
661+
[extent[2], extent[1]], // Bottom-right
662+
];
663+
664+
const pixelCoordinates = coordinates.map((coord) => this.map_.getPixelFromCoordinate(coord));
665+
666+
context.beginPath();
667+
668+
// outer rectangle
669+
context.rect(0, 0, canvas.width, canvas.height);
670+
671+
const width = pixelCoordinates[3][0] - pixelCoordinates[0][0];
672+
const height = pixelCoordinates[1][1] - pixelCoordinates[0][1];
673+
674+
// inner rectangle
675+
context.rect(pixelCoordinates[0][0], pixelCoordinates[0][1], width, height);
676+
677+
context.closePath();
678+
context.fillStyle = this.drawMaskColor_;
679+
context.fill('evenodd');
680+
}
617681
}

0 commit comments

Comments
 (0)