Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
giswqs
GitHub Repository: giswqs/geemap
Path: blob/master/js/layer_editor.ts
2313 views
1
import type { RenderProps } from "@anywidget/types";
2
import { css, html, TemplateResult } from "lit";
3
import { property, query } from "lit/decorators.js";
4
5
import { legacyStyles } from "./ipywidgets_styles";
6
import { LitWidget } from "./lit_widget";
7
import { RasterLayerEditor } from "./raster_layer_editor";
8
import { updateChildren } from "./utils";
9
import { VectorLayerEditor } from "./vector_layer_editor";
10
11
import "./container";
12
import "./palette_editor";
13
import "./raster_layer_editor";
14
import "./vector_layer_editor";
15
16
export interface LayerEditorModel {
17
layer_name: string;
18
layer_type: string;
19
children: Array<any>;
20
21
// Band names in the image (if applicable).
22
band_names: Array<string>;
23
// Colormaps available to select.
24
colormaps: Array<string>;
25
}
26
27
export class LayerEditor extends LitWidget<LayerEditorModel, LayerEditor> {
28
static get componentName(): string {
29
return `layer-editor`;
30
}
31
32
static override styles = [
33
legacyStyles,
34
css`
35
.confirm-button {
36
padding: 0 20px;
37
}
38
39
.confirm-button-row {
40
display: flex;
41
gap: 4px;
42
margin-top: 4px;
43
}
44
45
.editor-container {
46
max-height: 250px;
47
max-width: 350px;
48
overflow-y: auto;
49
}
50
`,
51
];
52
53
@property({ type: String }) layerName: string = "";
54
@property({ type: String }) layerType: string = "";
55
@property({ type: Array }) bandNames: Array<string> = [];
56
@property({ type: Array }) colormaps: Array<string> = [];
57
58
@query("raster-layer-editor") rasterEditor?: RasterLayerEditor;
59
@query("vector-layer-editor") vectorEditor?: VectorLayerEditor;
60
61
modelNameToViewName(): Map<
62
keyof LayerEditorModel,
63
keyof LayerEditor | null
64
> {
65
return new Map([
66
["layer_name", "layerName"],
67
["layer_type", "layerType"],
68
["band_names", "bandNames"],
69
["colormaps", "colormaps"],
70
["children", null],
71
]);
72
}
73
74
override onCustomMessage(msg: any): void {
75
const msgId = msg.id;
76
const response = msg.response;
77
78
if (msgId === "band-stats") {
79
this.handleBandStatsResponse(response);
80
} else if (msgId === "palette") {
81
this.handlePaletteResponse(response);
82
} else if (msgId === "fields") {
83
this.handleFieldResponse(response);
84
} else if (msgId === "field-values") {
85
this.handleFieldValuesResponse(response);
86
}
87
}
88
89
override render(): TemplateResult {
90
return html`
91
<widget-container
92
.title="${this.layerName}"
93
@close-clicked="${this.onCloseButtonClicked}"
94
>
95
<div class="editor-container">
96
${this.renderLayerEditorType()}
97
</div>
98
<div class="confirm-button-row">
99
<button
100
class="legacy-button primary confirm-button"
101
@click="${this.onImportClicked}"
102
>
103
Import
104
</button>
105
<button
106
class="legacy-button confirm-button"
107
@click="${this.onApplyClicked}"
108
>
109
Apply
110
</button>
111
</div>
112
</widget-container>
113
`;
114
}
115
116
private renderLayerEditorType(): TemplateResult {
117
if (this.layerType == "raster") {
118
return html`
119
<raster-layer-editor
120
.bandNames="${this.bandNames}"
121
.colormaps="${this.colormaps}"
122
@calculate-band-stats="${this.calculateBandStats}"
123
@calculate-palette="${this.calculatePalette}"
124
>
125
<slot></slot>
126
</raster-layer-editor>
127
`;
128
} else if (this.layerType == "vector") {
129
return html`
130
<vector-layer-editor
131
.newLayerName="${this.layerName + " style"}"
132
.colormaps="${this.colormaps}"
133
@calculate-fields="${this.calculateFields}"
134
@calculate-field-values="${this.calculateFieldValues}"
135
@calculate-palette="${this.calculatePalette}"
136
>
137
<slot></slot>
138
</vector-layer-editor>
139
`;
140
}
141
return html`<div><span>Vis params are uneditable</span></div>`;
142
}
143
144
private onCloseButtonClicked(_: Event): void {
145
this.model?.send({ type: "click", id: "close" });
146
}
147
148
private onApplyClicked(_event: Event): void {
149
this.sendCompletion("apply");
150
}
151
152
private onImportClicked(_event: Event): void {
153
this.sendCompletion("import");
154
}
155
156
private sendCompletion(completionType: string): void {
157
if (this.rasterEditor) {
158
this.model?.send({
159
id: completionType,
160
type: "click",
161
detail: this.rasterEditor.getVisualizationOptions(),
162
});
163
} else if (this.vectorEditor) {
164
this.model?.send({
165
id: completionType,
166
type: "click",
167
detail: this.vectorEditor.getVisualizationOptions(),
168
});
169
}
170
}
171
172
private calculateBandStats(_event: Event): void {
173
if (this.rasterEditor) {
174
this.model?.send({
175
id: "band-stats",
176
type: "calculate",
177
detail: {
178
bands: this.rasterEditor.selectedBands,
179
stretch: this.rasterEditor.stretch,
180
},
181
});
182
}
183
}
184
185
private handleBandStatsResponse(response: any): void {
186
if (this.rasterEditor) {
187
// Verify the stretch matches in case we get responses out-of-order.
188
if (response.stretch === this.rasterEditor.stretch) {
189
this.rasterEditor.minValue = response.min;
190
this.rasterEditor.maxValue = response.max;
191
}
192
}
193
}
194
195
private calculatePalette(event: CustomEvent): void {
196
this.model?.send({
197
id: "palette",
198
type: "calculate",
199
detail: {
200
colormap: event.detail.colormap,
201
classes: event.detail.classes,
202
palette: event.detail.palette,
203
bandMin: this.rasterEditor?.minValue ?? 0.0,
204
bandMax: this.rasterEditor?.maxValue ?? 1.0,
205
},
206
});
207
}
208
209
private getLegendClassLabels(palette: string): Array<string> {
210
if (palette === "") {
211
return [];
212
}
213
const length = palette.split(",").length;
214
return Array.from({ length }, (_, i) => `Class ${i + 1}`);
215
}
216
217
private handlePaletteResponse(response: any): void {
218
const paletteEditor = (this.rasterEditor || this.vectorEditor)
219
?.paletteEditor;
220
const legendCustomization = (this.rasterEditor || this.vectorEditor)?.legendCustomization;
221
if (paletteEditor && response.palette) {
222
paletteEditor.palette = response.palette;
223
}
224
if (legendCustomization) {
225
legendCustomization.labels = this.getLegendClassLabels(response.palette);
226
}
227
}
228
229
private calculateFields(_event: CustomEvent): void {
230
this.model?.send({ id: "fields", type: "calculate", detail: {} });
231
}
232
233
private handleFieldResponse(response: any): void {
234
if (this.vectorEditor) {
235
const fields = response.fields;
236
const values = response["field-values"];
237
this.vectorEditor.fields = fields;
238
this.vectorEditor.fieldValues = values;
239
this.vectorEditor.selectedField =
240
fields.length > 0 ? fields[0] : "";
241
this.vectorEditor.selectedFieldValue =
242
values.length > 0 ? values[0] : "";
243
}
244
}
245
246
private calculateFieldValues(_event: CustomEvent): void {
247
if (this.vectorEditor) {
248
this.model?.send({
249
id: "field-values",
250
type: "calculate",
251
detail: {
252
field: this.vectorEditor?.selectedField,
253
},
254
});
255
}
256
}
257
258
private handleFieldValuesResponse(response: any): void {
259
if (this.vectorEditor) {
260
const values = response["field-values"];
261
this.vectorEditor.fieldValues = values;
262
this.vectorEditor.selectedFieldValue =
263
values.length > 0 ? values[0] : "";
264
}
265
}
266
}
267
268
// Without this check, there's a component registry issue when developing locally.
269
if (!customElements.get(LayerEditor.componentName)) {
270
customElements.define(LayerEditor.componentName, LayerEditor);
271
}
272
273
async function render({ model, el }: RenderProps<LayerEditorModel>) {
274
const widget = document.createElement(LayerEditor.componentName) as LayerEditor;
275
widget.model = model;
276
el.appendChild(widget);
277
278
// Update the palette visualization.
279
updateChildren(widget, model);
280
model.on("change:children", () => {
281
updateChildren(widget, model);
282
});
283
}
284
285
export default { render };
286