Select to view content in your preferred language

Finding a Datasource at runtime

130
2
Jump to solution
Monday
JeffreyThompson2
MVP Frequent Contributor

I am working on a Custom Filter Widget that takes a Feature Layer with a Unique Value Renderer and turns it into a set of filters that includes the Legend swatches. It works. I love it. I'll put it on Community soon. But it's not my end goal. I am trying to build a series of Widgets on top of it and I'm stuck on the technical challenge of finding the DataSource of a FeatureLayer at runtime.

The current goal is a Widget that contains a dropdown menu. The user selects a layer in it and it makes the filter. The closest version I have so far is to create a DataSource in the higher level Widget and then pass it down to the lower level Filter Widget. This will read all the necessary data from the FeatureLayer and handle map interactions, but it won't filter the data either on the Map or in a Table. I think I'm actually creating a duplicate of the existing DataSource and filtering that, but I haven't found a way to grab the existing DataSource directly.

Here's my current code to create a DataSource in the wrapping Widget:

const fetchDataSourceJson = async (dsId: string, layer: object): Promise<IMDataSourceJson> => {
	const dsJson = await dataSourceJsonCreator.createDataSourceJsonByJSAPILayer(dsId, layer)
	return dsJson
}

const createDataSource = async (dsId: string, layer: object): Promise<DataSource> => {
	const dsJson = await fetchDataSourceJson(dsId, layer)
	const dsOptions: FeatureLayerDataSourceConstructorOptions = {
		id: dsId,
		dataSourceJson: dsJson
	}
	return DataSourceManager.getInstance().createDataSource(dsOptions)
}

const getDataSources = () => {
	const featureLayers = []
	const allLayers = jimuMapView.view.map.layers
	allLayers.forEach( async layer => {
		if (layer.type === 'feature') {
			console.log(layer)
			if (layer.renderer?.uniqueValueInfos) {
				const ds = await createDataSource(layer.id, layer)
				ds.layer = layer
				console.log(ds)
				featureLayers.push(ds)
			}
		}
	})
	setTimeout(() => {
		setAllDataSources(featureLayers)
	}, 500)
	
}

The completed Widget will look something like this.

JeffreyThompson2_0-1760993599243.png

 

GIS Developer
City of Arlington, Texas
0 Kudos
1 Solution

Accepted Solutions
JeffreyThompson2
MVP Frequent Contributor

Here is another version with a clause that captures an FeatureLayers added at runtime. Getting the DataSource for these was simple, but the layer contained within would always return undefined. I used the layer's title to search for the the layer in the mapView and associated it with the DataSource. This clause also had the side effect of duplicating the FeatureLayers in the Map inside a MapService. I added an additional clause to filter the duplicates back out.

	const getDataSources = () => {
		const featureLayers = []
		const dsManager = DataSourceManager.getInstance()
		const sources = dsManager.getDataSourcesAsArray()
		//find each webmap
		for (let i = 0; i < sources.length; i++) {
			const source = sources[i]
			if (source.type === 'WEB_MAP') {
				//createChildDataSources in webmap
				source.createChildDataSources().then(dss => {
					//loop through childDataSource
					dss.forEach(layerSource => {
						//if featureLayer check render type in map, get dataSource if uniqueValues
						if (layerSource.type === 'FEATURE_LAYER') {
							const layer = jimuMapView.view.map.findLayerById(layerSource.layer.id)
							if (layer.renderer.uniqueValueInfos) {
								featureLayers.push(layerSource)
							}
						}
						//if mapService createChildDataSources
						if (layerSource.type === 'MAP_SERVICE') {
							layerSource.createChildDataSources().then(childSource => {
								//loop through childDataSources, check if uniqueValues in map
								layerSource.layer.allSublayers.forEach((sublayer, j) => {
									if (sublayer.renderer.uniqueValueInfos) {
										featureLayers.push(childSource[j])
									}
								})

							})
						}
					})
				})
			//This clause is for sources added at runtime. The source is created, but the layer will be undefined. This finds the correct layer and associates it with the source. The source does not have an id that can be used to retrieve the layer from the mapView, so we match the title.
			} else if (source.type === 'FEATURE_LAYER' && !source.isDataView) {
				console.log(source)
				const name = source.dataSourceJson.sourceLabel
				const mapLayers = jimuMapView.view.map.layers.items
				let foundLayer = null
				for (let j = 0; j < mapLayers.length; j++) { 
					const layer = mapLayers[j]
					if (layer.title === name) {
						foundLayer = layer
						break
					}
					if (layer.sublayers) {
						const subLayers = layer.allSublayers.items
						for (let k = 0; k < subLayers.length; k++) {
							if (subLayers[k].title === name) {
								foundLayer = subLayers[k]
								break
							}
						}
						if (foundLayer) {
							break
						}
					}
				}
				if (foundLayer) {
					if (foundLayer.renderer.uniqueValueInfos) {
						source.layer = foundLayer
						featureLayers.push(source)
					}
				}
			}
		}
		//wait for load to memory
		setTimeout(() => {
			//layers from MapService in WebMap will be duplicated. this removes the duplicates. I know it's stupid. I really can't find a simplier way without blocking all runtime added layers.
			const filteredLayers = featureLayers.filter((obj, index, self) =>
				index === self.findIndex((t) => (
					t.id === obj.id
				))
			)
			setAllDataSources(filteredLayers)
		}, 500)
	}
GIS Developer
City of Arlington, Texas

View solution in original post

0 Kudos
2 Replies
JeffreyThompson2
MVP Frequent Contributor
const getDataSources = () => {
	const featureLayers = []
	const dsManager = DataSourceManager.getInstance()
	const sources = dsManager.getDataSourcesAsArray()
	console.log(sources)
	sources.forEach(source => {
		if (source.type === 'WEB_MAP') {
			console.log(source)
			source.createChildDataSources().then(dss => {
				console.log(dss)
				dss.forEach(layerSource => {
					if (layerSource.type === 'FEATURE_LAYER') {
						console.log(layerSource.layer)
						const layer = jimuMapView.view.map.findLayerById(layerSource.layer.id)
						if (layer.renderer.uniqueValueInfos) {
							featureLayers.push(layerSource)
						}
					}
					if (layerSource.type === 'MAP_SERVICE') {
						layerSource.createChildDataSources().then(childSource => {
							//console.log(childSource)
							layerSource.layer.allSublayers.forEach((sublayer, i) => {
								if (sublayer.renderer.uniqueValueInfos) {
									console.log(childSource[i])
									featureLayers.push(childSource[i])
								}
							})
								
						})
					}
				})
			})
		}
	})
	setTimeout(() => {
		setAllDataSources(featureLayers)
	}, 500)
}

After three days of tinkering, here is a function that will find all FeatureLayer DataSources within Map Widgets, including those inside a Map Service and check that they have a uniqueValueRenderer.

GIS Developer
City of Arlington, Texas
0 Kudos
JeffreyThompson2
MVP Frequent Contributor

Here is another version with a clause that captures an FeatureLayers added at runtime. Getting the DataSource for these was simple, but the layer contained within would always return undefined. I used the layer's title to search for the the layer in the mapView and associated it with the DataSource. This clause also had the side effect of duplicating the FeatureLayers in the Map inside a MapService. I added an additional clause to filter the duplicates back out.

	const getDataSources = () => {
		const featureLayers = []
		const dsManager = DataSourceManager.getInstance()
		const sources = dsManager.getDataSourcesAsArray()
		//find each webmap
		for (let i = 0; i < sources.length; i++) {
			const source = sources[i]
			if (source.type === 'WEB_MAP') {
				//createChildDataSources in webmap
				source.createChildDataSources().then(dss => {
					//loop through childDataSource
					dss.forEach(layerSource => {
						//if featureLayer check render type in map, get dataSource if uniqueValues
						if (layerSource.type === 'FEATURE_LAYER') {
							const layer = jimuMapView.view.map.findLayerById(layerSource.layer.id)
							if (layer.renderer.uniqueValueInfos) {
								featureLayers.push(layerSource)
							}
						}
						//if mapService createChildDataSources
						if (layerSource.type === 'MAP_SERVICE') {
							layerSource.createChildDataSources().then(childSource => {
								//loop through childDataSources, check if uniqueValues in map
								layerSource.layer.allSublayers.forEach((sublayer, j) => {
									if (sublayer.renderer.uniqueValueInfos) {
										featureLayers.push(childSource[j])
									}
								})

							})
						}
					})
				})
			//This clause is for sources added at runtime. The source is created, but the layer will be undefined. This finds the correct layer and associates it with the source. The source does not have an id that can be used to retrieve the layer from the mapView, so we match the title.
			} else if (source.type === 'FEATURE_LAYER' && !source.isDataView) {
				console.log(source)
				const name = source.dataSourceJson.sourceLabel
				const mapLayers = jimuMapView.view.map.layers.items
				let foundLayer = null
				for (let j = 0; j < mapLayers.length; j++) { 
					const layer = mapLayers[j]
					if (layer.title === name) {
						foundLayer = layer
						break
					}
					if (layer.sublayers) {
						const subLayers = layer.allSublayers.items
						for (let k = 0; k < subLayers.length; k++) {
							if (subLayers[k].title === name) {
								foundLayer = subLayers[k]
								break
							}
						}
						if (foundLayer) {
							break
						}
					}
				}
				if (foundLayer) {
					if (foundLayer.renderer.uniqueValueInfos) {
						source.layer = foundLayer
						featureLayers.push(source)
					}
				}
			}
		}
		//wait for load to memory
		setTimeout(() => {
			//layers from MapService in WebMap will be duplicated. this removes the duplicates. I know it's stupid. I really can't find a simplier way without blocking all runtime added layers.
			const filteredLayers = featureLayers.filter((obj, index, self) =>
				index === self.findIndex((t) => (
					t.id === obj.id
				))
			)
			setAllDataSources(filteredLayers)
		}, 500)
	}
GIS Developer
City of Arlington, Texas
0 Kudos