diff --git a/examples/buildings.jGIS b/examples/buildings.jGIS index b646d24cf..be824f6af 100644 --- a/examples/buildings.jGIS +++ b/examples/buildings.jGIS @@ -8,12 +8,12 @@ "name": "Buildings Vector Tile", "parameters": { "color": "#086600", - "opacity": 1.0, "source": "7a7ee6fd-c1e2-4c5d-a4e2-a7974db138a4", "type": "fill" }, "type": "VectorTileLayer", - "visible": true + "visible": true, + "opacity": 1 }, "f99eb7b0-5e38-4078-b310-36a0746472aa": { "name": "OpenStreetMap.Mapnik Layer", diff --git a/examples/cloud_optimized_geotiff.jGIS b/examples/cloud_optimized_geotiff.jGIS index 978694b3c..a476c8237 100644 --- a/examples/cloud_optimized_geotiff.jGIS +++ b/examples/cloud_optimized_geotiff.jGIS @@ -6,8 +6,8 @@ "layers": { "6dd80360-2675-40cf-aaee-c7101ef1779a": { "name": "OpenStreetMap.Mapnik Layer", + "opacity": 1.0, "parameters": { - "opacity": 1.0, "source": "a6a98ac4-0d1c-4a3d-af71-88a91f28ccb9" }, "type": "RasterLayer", @@ -15,6 +15,7 @@ }, "f75fd646-bc7d-478b-b65b-de34155b8efa": { "name": "COG Layer", + "opacity": 1.0, "parameters": { "color": [ "interpolate", @@ -138,7 +139,6 @@ 1.0 ] ], - "opacity": 1.0, "source": "8b1d4258-5d46-48da-b466-496d376b593d", "symbologyState": { "band": 1.0, diff --git a/examples/earthquakes.jGIS b/examples/earthquakes.jGIS index e3f69c384..be6f8f76a 100644 --- a/examples/earthquakes.jGIS +++ b/examples/earthquakes.jGIS @@ -6,6 +6,7 @@ "layers": { "8c99f4cd-849f-4d11-ae75-4100ea703f03": { "name": "Earthquakes", + "opacity": 1.0, "parameters": { "color": { "circle-fill-color": [ @@ -331,7 +332,6 @@ ], "stroke-width": 1.25 }, - "opacity": 1.0, "source": "578fb13e-9c82-4711-91f2-fb3671820583", "symbologyState": { "colorRamp": "viridis", @@ -347,8 +347,8 @@ }, "8de7c2c0-6024-4716-b542-031a89fb87f9": { "name": "OpenStreetMap.Mapnik Layer", + "opacity": 1.0, "parameters": { - "opacity": 1.0, "source": "b2ea427a-a51b-43ad-ae72-02cd900736d5" }, "type": "RasterLayer", diff --git a/examples/france_hiking.jGIS b/examples/france_hiking.jGIS index a65b6ee59..c28af8a61 100644 --- a/examples/france_hiking.jGIS +++ b/examples/france_hiking.jGIS @@ -7,8 +7,8 @@ "layers": { "0bfee293-9e2f-4434-8c5a-c90d19836bab": { "name": "WaymarkedTrails.hiking Layer", + "opacity": 1.0, "parameters": { - "opacity": 0.6, "source": "82691e55-f9e2-43be-8a07-3ae0409af7b4" }, "type": "RasterLayer", @@ -16,8 +16,8 @@ }, "4a0703b3-ed56-4158-8a2e-e008c3d0fee2": { "name": "OpenStreetMap.Mapnik Layer", + "opacity": 1.0, "parameters": { - "opacity": 1.0, "source": "60da082e-8b70-4fa2-b2f0-48524468fea0" }, "type": "RasterLayer", @@ -25,8 +25,8 @@ }, "7db81237-a579-4daa-938f-5e61fdfb17e7": { "name": "NASAGIBS.ModisTerraTrueColorCR Layer", + "opacity": 1.0, "parameters": { - "opacity": 0.3, "source": "52252f5d-3cb7-45a8-a724-5793bf9950ec" }, "type": "RasterLayer", diff --git a/examples/geoparquet.jGIS b/examples/geoparquet.jGIS index be79c0e38..d3abf8ebf 100644 --- a/examples/geoparquet.jGIS +++ b/examples/geoparquet.jGIS @@ -6,6 +6,7 @@ "layers": { "9556ca29-a5ec-41af-bf14-4b543c52aafe": { "name": "OpenStreetMap.Mapnik Layer", + "opacity": 1.0, "parameters": { "source": "2a52082b-7992-40dc-92d6-75e309a1ed27" }, @@ -14,8 +15,8 @@ }, "d7a2ad84-0750-4e9b-82c0-542fcc6b3265": { "name": "GeoParquet Vector Layer", + "opacity": 1.0, "parameters": { - "opacity": 1.0, "source": "c1da95b9-8a71-4fee-b4e3-6f0b5f53d2d4" }, "type": "VectorLayer", diff --git a/examples/hillshade.jGIS b/examples/hillshade.jGIS index b39ef2471..9a814e565 100644 --- a/examples/hillshade.jGIS +++ b/examples/hillshade.jGIS @@ -6,8 +6,8 @@ "layers": { "a82ef521-e727-4209-a5a0-145d66f18a06": { "name": "OpenStreetMap.Mapnik Layer", + "opacity": 1.0, "parameters": { - "opacity": 1.0, "source": "ceef4036-b757-44bf-8a21-42c6c99dab72" }, "type": "RasterLayer", @@ -15,6 +15,7 @@ }, "f12a8dfe-4674-43e7-a649-cd49ee83eb34": { "name": "Custom Hillshade Layer", + "opacity": 1.0, "parameters": { "shadowColor": "#473B24", "source": "e87bb91a-ff2a-485b-bf0e-14b3b848955a" diff --git a/examples/image.jGIS b/examples/image.jGIS index 7410fe44f..1d9fcb5b6 100644 --- a/examples/image.jGIS +++ b/examples/image.jGIS @@ -6,8 +6,8 @@ "layers": { "756cb737-f817-4ba6-b7c0-8da0b97b9778": { "name": "Radar", + "opacity": 1.0, "parameters": { - "opacity": 1.0, "source": "fb9729b8-82c6-48ac-a12b-6343c0e037ae" }, "type": "ImageLayer", @@ -15,6 +15,7 @@ }, "acb76fb7-df6d-41da-8e08-0208a1c82136": { "name": "OpenStreetMap.Mapnik Layer", + "opacity": 1.0, "parameters": { "source": "5f7a1edf-1b76-4f82-893f-540d28998b1d" }, diff --git a/examples/local.jGIS b/examples/local.jGIS index d7cc4d8e0..ac4d8cd60 100644 --- a/examples/local.jGIS +++ b/examples/local.jGIS @@ -7,8 +7,8 @@ "layers": { "66198b90-5d1c-4637-a15d-450b17c4fcb6": { "name": "Radar", + "opacity": 1.0, "parameters": { - "opacity": 1.0, "source": "756b4c42-6ca0-40c1-9e3b-90c1f5f99fab" }, "type": "ImageLayer", @@ -16,8 +16,8 @@ }, "aae4466d-49da-4308-b73d-046e974f92a4": { "name": "Subset of Manhattan roads", + "opacity": 1.0, "parameters": { - "opacity": 1.0, "source": "d82b24cf-e43a-43aa-bc7b-2578adc7ef8a" }, "type": "VectorLayer", @@ -25,6 +25,7 @@ }, "f5e91204-3ce9-408e-b179-c6cf5f75f0f7": { "name": "OpenStreetMap.Mapnik Layer", + "opacity": 1.0, "parameters": { "source": "3bd6b874-a28a-4ef6-bef8-a7257bb35c51" }, diff --git a/examples/pmtiles-raster.jGIS b/examples/pmtiles-raster.jGIS index 1f49488f5..05313779a 100644 --- a/examples/pmtiles-raster.jGIS +++ b/examples/pmtiles-raster.jGIS @@ -6,8 +6,8 @@ "layers": { "2815540d-70c6-4eed-ba86-51596adf6863": { "name": "Custom Raster Layer", + "opacity": 1.0, "parameters": { - "opacity": 0.5, "source": "d3a5ad67-d6fe-4793-a6c5-dd773d76c745" }, "type": "RasterLayer", @@ -15,6 +15,7 @@ }, "3d4563da-904d-4026-a06b-1e8cffbf536f": { "name": "OpenStreetMap.Mapnik Layer", + "opacity": 1.0, "parameters": { "source": "d76035d6-9fb2-41db-ad32-ea4d34268dc9" }, diff --git a/examples/pmtiles-vector.jGIS b/examples/pmtiles-vector.jGIS index 92bd14b75..e43e62dcb 100644 --- a/examples/pmtiles-vector.jGIS +++ b/examples/pmtiles-vector.jGIS @@ -6,6 +6,7 @@ "layers": { "93077483-0e49-4135-aa1d-244e29e5cf97": { "name": "OpenStreetMap.Mapnik Layer", + "opacity": 1.0, "parameters": { "source": "c42d25fc-ca10-4447-ba00-ba22b0367739" }, @@ -14,9 +15,9 @@ }, "95523f4c-27ef-42db-b755-a226ca017a94": { "name": "Buildings", + "opacity": 1.0, "parameters": { "color": "#FF0000", - "opacity": 1.0, "source": "e52daf20-9b57-4a14-9521-70eabbe4cda2", "type": "line" }, diff --git a/examples/roads.jGIS b/examples/roads.jGIS index a9475eef5..321291d96 100644 --- a/examples/roads.jGIS +++ b/examples/roads.jGIS @@ -5,6 +5,7 @@ "layers": { "95bfd9ba-f561-414d-8e2b-bc401dae0d7f": { "name": "Roads", + "opacity": 1.0, "parameters": { "color": { "stroke-color": [ @@ -7451,7 +7452,6 @@ ] ] }, - "opacity": 1.0, "source": "b9e30ab1-864e-4395-9071-cddb6ac813e5", "symbologyState": { "colorRamp": "rainbow", diff --git a/examples/shapefile.jGIS b/examples/shapefile.jGIS index d77f850e0..4e5c98bd9 100644 --- a/examples/shapefile.jGIS +++ b/examples/shapefile.jGIS @@ -7,6 +7,7 @@ "layers": { "54e0ea1f-32fd-4ff2-90fb-ad449a38f902": { "name": "OpenStreetMap.Mapnik Layer", + "opacity": 1.0, "parameters": { "source": "780c4234-ba6d-4da2-9731-739f6c4d8033" }, @@ -15,8 +16,8 @@ }, "df947252-afb1-489f-8bce-7057318a50e3": { "name": "Subset of Manhattan roads", + "opacity": 1.0, "parameters": { - "opacity": 1.0, "source": "c3215d81-4b47-4b93-ae1c-5eb0fe8a68a8" }, "type": "VectorLayer", @@ -24,6 +25,7 @@ }, "f909d9b9-bf07-42f6-8841-8d6470665dcb": { "name": "Administrative boundaries", + "opacity": 1.0, "parameters": { "color": { "fill-color": "#eaa4e7", @@ -32,7 +34,6 @@ "stroke-line-join": "round", "stroke-width": 1.25 }, - "opacity": 1.0, "source": "eb4b68da-eab6-44a1-8e75-ca9c871d2755", "symbologyState": { "renderType": "Single Symbol" diff --git a/examples/test.jGIS b/examples/test.jGIS index 23400c9f9..a2d27521c 100644 --- a/examples/test.jGIS +++ b/examples/test.jGIS @@ -17,8 +17,8 @@ "layers": { "2467576f-b527-4cb7-998d-fa1d056fb8a1": { "name": "Open Street Map", + "opacity": 1.0, "parameters": { - "opacity": 1.0, "source": "699facc9-e7c4-4f38-acf1-1fd7f02d9f36" }, "type": "RasterLayer", @@ -26,9 +26,9 @@ }, "57ef55ef-facb-48a2-ae1d-c9c824be3e8a": { "name": "Regions France", + "opacity": 0.6, "parameters": { "color": "#e66100", - "opacity": 0.6, "source": "7d082e75-69d5-447a-82d8-b05cca5945ba", "type": "line" }, @@ -37,8 +37,8 @@ }, "a0044fd7-f167-445f-b3d1-620a8f94b498": { "name": "Open Topo Map", + "opacity": 1.0, "parameters": { - "opacity": 1.0, "source": "5fd42e3b-4681-4607-b15d-65c3a3e89b32" }, "type": "RasterLayer", diff --git a/examples/world.jGIS b/examples/world.jGIS index beba6b7a5..c2d25a3b8 100644 --- a/examples/world.jGIS +++ b/examples/world.jGIS @@ -6,6 +6,7 @@ "layers": { "6e55cdae-35b0-4bff-9dd1-c8aa9563b2a6": { "name": "World", + "opacity": 1.0, "parameters": { "color": { "fill-color": [ @@ -82,7 +83,6 @@ ] ] }, - "opacity": 1.0, "source": "b4287bea-e217-443c-b527-58f7559c824c", "symbologyState": { "colorRamp": "cool", @@ -99,6 +99,7 @@ }, "f80d0fa2-3e2b-4922-b7d5-fefd4b085259": { "name": "France", + "opacity": 1.0, "parameters": { "color": { "fill-color": "#ff0000", @@ -107,7 +108,6 @@ "stroke-line-join": "round", "stroke-width": 1.25 }, - "opacity": 1.0, "source": "5970d6c9-26be-4cc6-84c9-16593dd3edfe", "symbologyState": { "renderType": "Single Symbol" diff --git a/packages/base/src/formbuilder/creationform.tsx b/packages/base/src/formbuilder/creationform.tsx index 16f68db11..45a4cdc23 100644 --- a/packages/base/src/formbuilder/creationform.tsx +++ b/packages/base/src/formbuilder/creationform.tsx @@ -111,6 +111,14 @@ export class CreationForm extends React.Component { layerSchema['required'] = ['name', ...layerSchema['required']]; layerSchema['properties'] = { name: { type: 'string', description: 'The name of the layer' }, + opacity: { + type: 'number', + description: 'The opacity of the object', + default: 1, + multipleOf: 0.1, + minimum: 0, + maximum: 1, + }, ...layerSchema['properties'], }; } @@ -173,7 +181,7 @@ export class CreationForm extends React.Component { if (this.props.createLayer) { let actualName = ''; - const { name, ...layerData } = + const { name, opacity, ...layerData } = (await layerCreationPromise?.promise) as IDict; actualName = @@ -184,6 +192,7 @@ export class CreationForm extends React.Component { type: this.props.layerType || 'RasterLayer', parameters: layerData, visible: true, + opacity: opacity ?? 1, name: actualName, }; diff --git a/packages/base/src/formbuilder/editform.tsx b/packages/base/src/formbuilder/editform.tsx index 6560a1dc2..fe84faaa9 100644 --- a/packages/base/src/formbuilder/editform.tsx +++ b/packages/base/src/formbuilder/editform.tsx @@ -39,7 +39,7 @@ export class EditForm extends React.Component { return; } - this.props.model.sharedModel.updateObjectParameters(id, properties); + this.props.model.sharedModel.updateObject(id, properties); } render() { @@ -53,11 +53,28 @@ export class EditForm extends React.Component { } LayerForm = getLayerTypeForm(layer?.type || 'RasterLayer'); - layerData = deepCopy(layer?.parameters || {}); + layerData = { + opacity: layer?.opacity ?? 1, + ...deepCopy(layer?.parameters || {}), + }; layerSchema = deepCopy( this.props.formSchemaRegistry.getSchemas().get(layer.type), ); + if (layerSchema) { + layerSchema['properties'] = { + ...layerSchema['properties'], + opacity: { + type: 'number', + description: 'The opacity of the source', + default: 1, + multipleOf: 0.1, + minimum: 0, + maximum: 1, + }, + }; + } + if (!layerSchema) { console.error(`Cannot find schema for ${layer.type}`); return; @@ -99,7 +116,16 @@ export class EditForm extends React.Component { schema={layerSchema} sourceData={layerData} syncData={(properties: { [key: string]: any }) => { - this.syncObjectProperties(this.props.layer, properties); + if (!this.props.layer) { + return; + } + + const { opacity, ...params } = properties; + + this.props.model.sharedModel.updateObject(this.props.layer, { + opacity, + parameters: params, + }); }} /> diff --git a/packages/base/src/mainview/mainView.tsx b/packages/base/src/mainview/mainView.tsx index cd69f6eb9..dc2863e66 100644 --- a/packages/base/src/mainview/mainView.tsx +++ b/packages/base/src/mainview/mainView.tsx @@ -26,7 +26,6 @@ import { IShapefileSource, IStacLayer, IVectorLayer, - IVectorTileLayer, IVectorTileSource, IGeoParquetSource, IWebGlLayer, @@ -315,10 +314,10 @@ export class MainView extends React.Component { const layerModel: IJGISLayer = { type: 'VectorLayer', visible: true, + opacity: 1.0, name: 'Drag and Drop layer', parameters: { color: '#FF0000', - opacity: 1.0, type: 'line', source: sourceId, }, @@ -986,7 +985,7 @@ export class MainView extends React.Component { layerParameters = layer.parameters as IRasterLayer; newMapLayer = new TileLayer({ - opacity: layerParameters.opacity, + opacity: layer.opacity, visible: layer.visible, source: this._sources[layerParameters.source], }); @@ -997,7 +996,7 @@ export class MainView extends React.Component { layerParameters = layer.parameters as IVectorLayer; newMapLayer = new VectorLayer({ - opacity: layerParameters.opacity, + opacity: layer.opacity, visible: layer.visible, source: this._sources[layerParameters.source], style: this.vectorLayerStyleRuleBuilder(layer), @@ -1009,7 +1008,7 @@ export class MainView extends React.Component { layerParameters = layer.parameters as IVectorLayer; newMapLayer = new VectorTileLayer({ - opacity: layerParameters.opacity, + opacity: layer.opacity, source: this._sources[layerParameters.source], style: this.vectorLayerStyleRuleBuilder(layer), }); @@ -1033,7 +1032,7 @@ export class MainView extends React.Component { layerParameters = layer.parameters as IImageLayer; newMapLayer = new ImageLayer({ - opacity: layerParameters.opacity, + opacity: layer.opacity, source: this._sources[layerParameters.source], }); @@ -1044,7 +1043,7 @@ export class MainView extends React.Component { // This is to handle python sending a None for the color const layerOptions: any = { - opacity: layerParameters.opacity, + opacity: layer.opacity, source: this._sources[layerParameters.source], }; @@ -1061,7 +1060,7 @@ export class MainView extends React.Component { layerParameters = layer.parameters as IHeatmapLayer; newMapLayer = new HeatmapLayer({ - opacity: layerParameters.opacity, + opacity: layer.opacity, source: this._sources[layerParameters.source], blur: layerParameters.blur ?? 15, radius: layerParameters.radius ?? 8, @@ -1076,7 +1075,7 @@ export class MainView extends React.Component { newMapLayer = new StacLayer({ displayPreview: true, data: layerParameters.data, - opacity: layerParameters.opacity, + opacity: layer.opacity, visible: layer.visible, assets: Object.keys(layerParameters.data.assets), extent: layerParameters.data.bbox, @@ -1313,13 +1312,11 @@ export class MainView extends React.Component { switch (layer.type) { case 'RasterLayer': { - mapLayer.setOpacity(layer.parameters?.opacity || 1); + mapLayer.setOpacity(layer.opacity || 1); break; } case 'VectorLayer': { - const layerParams = layer.parameters as IVectorLayer; - - mapLayer.setOpacity(layerParams.opacity || 1); + mapLayer.setOpacity(layer.opacity || 1); (mapLayer as VectorLayer).setStyle( this.vectorLayerStyleRuleBuilder(layer), @@ -1328,9 +1325,7 @@ export class MainView extends React.Component { break; } case 'VectorTileLayer': { - const layerParams = layer.parameters as IVectorTileLayer; - - mapLayer.setOpacity(layerParams.opacity || 1); + mapLayer.setOpacity(layer.opacity || 1); (mapLayer as VectorTileLayer).setStyle( this.vectorLayerStyleRuleBuilder(layer), @@ -1346,7 +1341,7 @@ export class MainView extends React.Component { break; } case 'WebGlLayer': { - mapLayer.setOpacity(layer.parameters?.opacity); + mapLayer.setOpacity(layer.opacity || 1); if (layer?.parameters?.color) { (mapLayer as WebGlTileLayer).setStyle({ @@ -1359,7 +1354,7 @@ export class MainView extends React.Component { const layerParams = layer.parameters as IHeatmapLayer; const heatmap = mapLayer as HeatmapLayer; - heatmap.setOpacity(layerParams.opacity ?? 1); + heatmap.setOpacity(layer.opacity ?? 1); heatmap.setBlur(layerParams.blur ?? 15); heatmap.setRadius(layerParams.radius ?? 8); heatmap.setGradient( @@ -1371,7 +1366,7 @@ export class MainView extends React.Component { break; } case 'StacLayer': - mapLayer.setOpacity(layer.parameters?.opacity || 1); + mapLayer.setOpacity(layer.opacity || 1); break; } } diff --git a/packages/base/src/processing/index.ts b/packages/base/src/processing/index.ts index 20b1942b9..d8f3f8759 100644 --- a/packages/base/src/processing/index.ts +++ b/packages/base/src/processing/index.ts @@ -244,6 +244,7 @@ export async function executeSQLProcessing( type: 'VectorLayer', parameters: { source: newSourceId }, visible: true, + opacity: 1.0, name: layerName, }; @@ -264,6 +265,7 @@ export async function executeSQLProcessing( type: 'VectorLayer', parameters: { source: newSourceId }, visible: true, + opacity: 1.0, name: layerName, }; diff --git a/packages/base/src/stacBrowser/hooks/useStacSearch.ts b/packages/base/src/stacBrowser/hooks/useStacSearch.ts index 5d07ace63..5c2062229 100644 --- a/packages/base/src/stacBrowser/hooks/useStacSearch.ts +++ b/packages/base/src/stacBrowser/hooks/useStacSearch.ts @@ -251,6 +251,7 @@ function useStacSearch({ model }: IUseStacSearchProps): IUseStacSearchReturn { type: 'StacLayer', parameters: { data: stacData }, visible: true, + opacity: 1.0, name: stacData.properties.title ?? stacData.id, }; diff --git a/packages/schema/src/doc.ts b/packages/schema/src/doc.ts index 2f7f6619c..a0bba1ff4 100644 --- a/packages/schema/src/doc.ts +++ b/packages/schema/src/doc.ts @@ -227,28 +227,37 @@ export class JupyterGISDoc } } - updateObjectParameters( - id: string, - value: IJGISLayer['parameters'] | IJGISSource['parameters'], - ) { + updateObject(id: string, value: Partial | Partial) { const layer = this.getLayer(id); if (layer) { - layer.parameters = { - ...layer.parameters, - ...value, + const layerValue = value as Partial; + + const updatedLayer: IJGISLayer = { + ...layer, + ...layerValue, + parameters: { + ...layer.parameters, + ...(layerValue.parameters || {}), + }, }; - - this.updateLayer(id, layer); + this.updateLayer(id, updatedLayer); + return; } const source = this.getLayerSource(id); if (source) { - source.parameters = { - ...source.parameters, - ...value, + const sourceValue = value as Partial; + + const updatedSource: IJGISSource = { + ...source, + ...sourceValue, + parameters: { + ...source.parameters, + ...(sourceValue.parameters || {}), + }, }; - - this.updateSource(id, source); + this.updateSource(id, updatedSource); + return; } } diff --git a/packages/schema/src/interfaces.ts b/packages/schema/src/interfaces.ts index 248c090b0..42b01a02c 100644 --- a/packages/schema/src/interfaces.ts +++ b/packages/schema/src/interfaces.ts @@ -122,9 +122,9 @@ export interface IJupyterGISDoc extends YDocument { addLayerTreeItem(index: number, item: IJGISLayerItem): void; updateLayerTreeItem(index: number, item: IJGISLayerItem): void; - updateObjectParameters( + updateObject( id: string, - value: IJGISLayer['parameters'] | IJGISSource['parameters'], + value: Partial | Partial, ): void; getObject(id: string): IJGISLayer | IJGISSource | undefined; diff --git a/packages/schema/src/schema/project/jgis.json b/packages/schema/src/schema/project/jgis.json index b710f415a..115d1e3b7 100644 --- a/packages/schema/src/schema/project/jgis.json +++ b/packages/schema/src/schema/project/jgis.json @@ -74,6 +74,14 @@ "type": "boolean", "default": true }, + "opacity": { + "type": "number", + "description": "The opacity of the source", + "default": 1, + "multipleOf": 0.1, + "minimum": 0, + "maximum": 1 + }, "parameters": { "type": "object" }, diff --git a/packages/schema/src/schema/project/layers/heatmapLayer.json b/packages/schema/src/schema/project/layers/heatmapLayer.json index 4231308a1..a37731dd2 100644 --- a/packages/schema/src/schema/project/layers/heatmapLayer.json +++ b/packages/schema/src/schema/project/layers/heatmapLayer.json @@ -9,14 +9,6 @@ "type": "string", "description": "The id of the source" }, - "opacity": { - "type": "number", - "description": "The opacity of the source", - "default": 1, - "multipleOf": 0.1, - "minimum": 0, - "maximum": 1 - }, "radius": { "type": "number", "description": "Radius size in pixels", diff --git a/packages/schema/src/schema/project/layers/imageLayer.json b/packages/schema/src/schema/project/layers/imageLayer.json index 39ac323f5..c50f35a63 100644 --- a/packages/schema/src/schema/project/layers/imageLayer.json +++ b/packages/schema/src/schema/project/layers/imageLayer.json @@ -8,14 +8,6 @@ "source": { "type": "string", "description": "The id of the source" - }, - "opacity": { - "type": "number", - "description": "The opacity of the source", - "default": 1, - "multipleOf": 0.1, - "minimum": 0, - "maximum": 1 } } } diff --git a/packages/schema/src/schema/project/layers/rasterLayer.json b/packages/schema/src/schema/project/layers/rasterLayer.json index c42509cba..00d70fec2 100644 --- a/packages/schema/src/schema/project/layers/rasterLayer.json +++ b/packages/schema/src/schema/project/layers/rasterLayer.json @@ -8,14 +8,6 @@ "source": { "type": "string", "description": "The id of the source" - }, - "opacity": { - "type": "number", - "description": "The opacity of the source", - "default": 1, - "multipleOf": 0.1, - "minimum": 0, - "maximum": 1 } } } diff --git a/packages/schema/src/schema/project/layers/stacLayer.json b/packages/schema/src/schema/project/layers/stacLayer.json index 7cb7d320c..c5aac9795 100644 --- a/packages/schema/src/schema/project/layers/stacLayer.json +++ b/packages/schema/src/schema/project/layers/stacLayer.json @@ -8,14 +8,6 @@ "data": { "type": "object", "description": "The data of the source" - }, - "opacity": { - "type": "number", - "description": "The opacity of the source", - "default": 1, - "multipleOf": 0.1, - "minimum": 0, - "maximum": 1 } } } diff --git a/packages/schema/src/schema/project/layers/vectorLayer.json b/packages/schema/src/schema/project/layers/vectorLayer.json index 992777ff1..31fc54cb0 100644 --- a/packages/schema/src/schema/project/layers/vectorLayer.json +++ b/packages/schema/src/schema/project/layers/vectorLayer.json @@ -13,14 +13,6 @@ "type": "object", "description": "The color of the the object" }, - "opacity": { - "type": "number", - "description": "The opacity of the the object", - "default": 1, - "multipleOf": 0.1, - "minimum": 0, - "maximum": 1 - }, "symbologyState": { "type": "object", "description": "The state of the symbology panel options", diff --git a/packages/schema/src/schema/project/layers/vectorTileLayer.json b/packages/schema/src/schema/project/layers/vectorTileLayer.json index 1fd9cc6fb..f4f52e5ec 100644 --- a/packages/schema/src/schema/project/layers/vectorTileLayer.json +++ b/packages/schema/src/schema/project/layers/vectorTileLayer.json @@ -12,14 +12,6 @@ "color": { "type": "object", "description": "The color of the the object" - }, - "opacity": { - "type": "number", - "description": "The opacity of the the object", - "default": 1, - "multipleOf": 0.1, - "minimum": 0, - "maximum": 1 } } } diff --git a/packages/schema/src/schema/project/layers/webGlLayer.json b/packages/schema/src/schema/project/layers/webGlLayer.json index d88213079..849ef2c7d 100644 --- a/packages/schema/src/schema/project/layers/webGlLayer.json +++ b/packages/schema/src/schema/project/layers/webGlLayer.json @@ -9,14 +9,6 @@ "type": "string", "description": "The id of the source" }, - "opacity": { - "type": "number", - "description": "The opacity of the source", - "default": 1, - "multipleOf": 0.1, - "minimum": 0, - "maximum": 1 - }, "color": { "oneOf": [ { "type": "string" }, diff --git a/python/jupytergis_core/jupytergis_core/jgis_ydoc.py b/python/jupytergis_core/jupytergis_core/jgis_ydoc.py index 10ca54912..22c65ea63 100644 --- a/python/jupytergis_core/jupytergis_core/jgis_ydoc.py +++ b/python/jupytergis_core/jupytergis_core/jgis_ydoc.py @@ -61,6 +61,16 @@ def set(self, value: str) -> None: if file_version > Version(SCHEMA_VERSION): raise ValueError(f"Cannot load file version {file_version}") + if file_version < Version("0.8.1"): + for _layer_id, layer in valueDict.get("layers", {}).items(): + params = layer.get("parameters", {}) + if "opacity" in params: + # only set top-level opacity if missing + if "opacity" not in layer: + layer["opacity"] = params["opacity"] + # remove from parameters + del params["opacity"] + with self._ydoc.transaction(): self._ylayers.clear() self._ylayers.update(valueDict.get("layers", {})) diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py index 03319d0dd..d01bcf8c9 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py @@ -175,7 +175,8 @@ def add_raster_layer( "type": LayerType.RasterLayer, "name": name, "visible": True, - "parameters": {"source": source_id, "opacity": opacity}, + "opacity": opacity, + "parameters": {"source": source_id}, } return self._add_layer(OBJECT_FACTORY.create_layer(layer, self)) @@ -224,11 +225,10 @@ def add_vectortile_layer( "type": LayerType.VectorTileLayer, "name": name, "visible": True, + "opacity": opacity, "parameters": { "source": source_id, - "opacity": opacity, "color": color_expr, - "opacity": opacity, }, "filters": { "appliedFilters": [ @@ -299,10 +299,10 @@ def add_geojson_layer( "type": LayerType.VectorLayer, "name": name, "visible": True, + "opacity": opacity, "parameters": { "source": source_id, "color": color_expr, - "opacity": opacity, }, "filters": { "appliedFilters": [ @@ -345,7 +345,8 @@ def add_image_layer( "type": LayerType.ImageLayer, "name": name, "visible": True, - "parameters": {"source": source_id, "opacity": opacity}, + "opacity": opacity, + "parameters": {"source": source_id}, } return self._add_layer(OBJECT_FACTORY.create_layer(layer, self)) @@ -383,7 +384,8 @@ def add_video_layer( "type": LayerType.RasterLayer, "name": name, "visible": True, - "parameters": {"source": source_id, "opacity": opacity}, + "opacity": opacity, + "parameters": {"source": source_id}, } return self._add_layer(OBJECT_FACTORY.create_layer(layer, self)) @@ -428,9 +430,9 @@ def add_tiff_layer( "type": LayerType.WebGlLayer, "name": name, "visible": True, + "opacity": opacity, "parameters": { "source": source_id, - "opacity": opacity, "color": color_expr, }, } @@ -532,11 +534,11 @@ def add_heatmap_layer( "type": LayerType.HeatmapLayer, "name": name, "visible": True, + "opacity": opacity, "parameters": { "source": source_id, "type": type, "color": gradient, - "opacity": opacity, "blur": blur, "radius": radius, "feature": feature, @@ -583,10 +585,10 @@ def add_geoparquet_layer( "type": LayerType.VectorLayer, "name": name, "visible": True, + "opacity": opacity, "parameters": { "source": source_id, "type": type, - "opacity": opacity, "color": color_expr, }, "filters": { diff --git a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py index 7d301bfe8..9a0cc79bc 100644 --- a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py +++ b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py @@ -298,6 +298,8 @@ def qgis_layer_to_jgis( if symbol: # Opacity handling opacity = symbol.opacity() + layer_parameters["opacity"] = opacity + alpha = hex(int(opacity * 255))[2:].zfill(2) if isinstance(symbol, QgsMarkerSymbol): @@ -390,13 +392,13 @@ def qgis_layer_to_jgis( source_name = f"{layer_name} Source" layer_parameters["source"] = source_id - layer_parameters["opacity"] = layer.opacity() layers[layer_id] = { "name": layer_name, "parameters": layer_parameters, "type": layer_type, "visible": is_visible, + "opacity": layer.opacity(), } sources[source_id] = { "name": source_name, @@ -517,6 +519,9 @@ def import_project_from_qgis(path: str | Path): def get_base_symbol(geometry_type, color_params, opacity): """Returns a base symbol based on geometry type.""" + if opacity is None: + opacity = 1.0 + if geometry_type == "circle": symbol = QgsMarkerSymbol() elif geometry_type == "line": @@ -786,7 +791,7 @@ def build_uri(parameters: dict[str, str], source_type: str) -> str | None: if geometry_type == "circle": symbol = QgsMarkerSymbol() color_params = layer_params.get("color", {}) - opacity = layer_params.get("opacity", 1.0) + opacity = layer.get("opacity", 1.0) symbology_state = layer_params.get("symbologyState", {}) render_type = symbology_state.get("renderType", "Single Symbol") @@ -816,7 +821,7 @@ def build_uri(parameters: dict[str, str], source_type: str) -> str | None: symbol.setOutputUnit(Qgis.RenderUnit.Pixels) color_params = layer_params.get("color", {}) - opacity = layer_params.get("opacity") + opacity = layer.get("opacity") symbology_state = layer_params.get("symbologyState", {}) render_type = symbology_state.get("renderType", "Single Symbol") @@ -844,7 +849,7 @@ def build_uri(parameters: dict[str, str], source_type: str) -> str | None: symbol = QgsFillSymbol() symbol.setOutputUnit(Qgis.RenderUnit.Pixels) color_params = layer_params.get("color", {}) - opacity = layer_params.get("opacity", 1.0) + opacity = layer.get("opacity", 1.0) symbology_state = layer_params.get("symbologyState", {}) render_type = symbology_state.get("renderType", "Single Symbol") @@ -985,7 +990,7 @@ def build_uri(parameters: dict[str, str], source_type: str) -> str | None: return map_layer.setId(layer_id) - map_layer.setOpacity(layer.get("parameters", {}).get("opacity", 1.0)) + map_layer.setOpacity(layer.get("opacity", 1.0)) # Map the source id/name to the layer layerSourceMap = settings.value("layerSourceMap", {}) diff --git a/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py b/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py index 377badc39..beb4a1f9b 100644 --- a/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py +++ b/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py @@ -32,38 +32,38 @@ def test_qgis_loader(): "_02b1b4d5_316b_4f4d_9c38_16bf10a3bcb8": { "name": "OpenStreetMap0", "parameters": { - "opacity": 1.0, "source": source_id0, }, "type": "RasterLayer", "visible": True, + "opacity": 1.0, }, "_097deeeb_6564_48d1_a3be_1caa4d93382f": { "name": "OpenStreetMap1", "parameters": { - "opacity": 1.0, "source": source_id1, }, "type": "RasterLayer", "visible": True, + "opacity": 1.0, }, "_bccce044_998d_45f9_bf6b_fe1472681cc3": { "name": "OpenStreetMap2", "parameters": { - "opacity": 1.0, "source": source_id2, }, "type": "RasterLayer", "visible": True, + "opacity": 1.0, }, "_32a77a2c_1756_4876_9f99_e3c7b702f86a": { "name": "OpenStreetMap3", "parameters": { - "opacity": 1.0, "source": source_id3, }, "type": "RasterLayer", "visible": True, + "opacity": 1.0, }, }, layerTree=[ @@ -120,6 +120,21 @@ def test_qgis_loader(): ) +def normalize_jgis(jgis): + import copy + + norm = copy.deepcopy(jgis) + + for layer in norm["layers"].values(): + if "opacity" not in layer: + layer["opacity"] = 1.0 + + if "parameters" in layer and "opacity" in layer["parameters"]: + del layer["parameters"]["opacity"] + + return norm + + def test_qgis_saver(): filename = FILES / "project1.qgz" if os.path.exists(filename): @@ -160,25 +175,24 @@ def test_qgis_saver(): layer_ids[0]: { "name": "OpenStreetMap0", "parameters": { - "opacity": 1.0, "source": source_ids[0], }, "type": "RasterLayer", "visible": True, + "opacity": 1.0, }, layer_ids[1]: { "name": "OpenStreetMap1", "parameters": { - "opacity": 1.0, "source": source_ids[1], }, "type": "RasterLayer", "visible": True, + "opacity": 1.0, }, layer_ids[2]: { "name": "Vector Tile Layer", "parameters": { - "opacity": 1.0, "color": { "circle-fill-color": "#e1598987", "circle-stroke-color": "#e1598987", @@ -190,15 +204,16 @@ def test_qgis_saver(): }, "type": "VectorTileLayer", "visible": True, + "opacity": 1.0, }, layer_ids[3]: { "name": "OpenStreetMap3", "parameters": { - "opacity": 1.0, "source": source_ids[3], }, "type": "RasterLayer", "visible": False, + "opacity": 1.0, }, layer_ids[4]: { "name": "Custom GeoJSON Layer", @@ -207,13 +222,13 @@ def test_qgis_saver(): "fill-color": "#4ea4d0", "stroke-color": "#4ea4d0", }, - "opacity": 1.0, "source": source_ids[4], "symbologyState": {"renderType": "Single Symbol"}, "type": "fill", }, "type": "VectorLayer", "visible": True, + "opacity": 1.0, }, layer_ids[5]: { "name": "Custom GeoJSON Layer", @@ -244,7 +259,6 @@ def test_qgis_saver(): ], "stroke-color": "#000000", }, - "opacity": 1.0, "source": source_ids[5], "symbologyState": { "renderType": "Graduated", @@ -254,6 +268,7 @@ def test_qgis_saver(): }, "type": "VectorLayer", "visible": True, + "opacity": 1.0, }, layer_ids[6]: { "name": "Custom GeoJSON Layer", @@ -299,7 +314,6 @@ def test_qgis_saver(): "stroke-line-join": "bevel", "stroke-width": 1.0, }, - "opacity": 1.0, "source": source_ids[6], "symbologyState": { "colorRamp": "viridis", @@ -312,6 +326,7 @@ def test_qgis_saver(): }, "type": "VectorLayer", "visible": True, + "opacity": 1.0, }, }, "layerTree": [ @@ -394,4 +409,4 @@ def test_qgis_saver(): imported_jgis = import_project_from_qgis(filename) - assert jgis == imported_jgis + assert normalize_jgis(jgis) == normalize_jgis(imported_jgis) diff --git a/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-0-chromium-linux.png b/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-0-chromium-linux.png index fdf6fb68f..56e5ebec0 100644 Binary files a/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-0-chromium-linux.png and b/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-0-chromium-linux.png differ diff --git a/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-1-chromium-linux.png b/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-1-chromium-linux.png index f2723d5ad..27b72e391 100644 Binary files a/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-1-chromium-linux.png and b/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-1-chromium-linux.png differ diff --git a/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-2-chromium-linux.png b/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-2-chromium-linux.png index 8c49e0534..dbd593d8d 100644 Binary files a/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-2-chromium-linux.png and b/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-2-chromium-linux.png differ diff --git a/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-0-chromium-linux.png b/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-0-chromium-linux.png index 9094ae9e9..5b10f9016 100644 Binary files a/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-0-chromium-linux.png and b/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-0-chromium-linux.png differ diff --git a/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-1-chromium-linux.png b/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-1-chromium-linux.png index 3a9cbfc6d..1a17d9bd9 100644 Binary files a/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-1-chromium-linux.png and b/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-1-chromium-linux.png differ diff --git a/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-2-chromium-linux.png b/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-2-chromium-linux.png index c0b466df5..c6130be3c 100644 Binary files a/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-2-chromium-linux.png and b/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-2-chromium-linux.png differ