** Audience:** Core developers extending GeoView functionality
For API Users: See GeoView Layers Guide for using existing layer types
This guide explains how to add support for new layer types to GeoView.
GeoView layers are divided into two categories:
- Raster - Image-based layers (managed by
AbstractGeoViewRaster) - Vector - Geometry-based layers (managed by
AbstractGeoViewVector)
Both categories extend the parent abstract class AbstractGeoViewLayers.
AbstractGeoViewLayers (parent)
+-- AbstractGeoViewRaster
| +-- EsriDynamic
| +-- EsriImage
| +-- ImageStatic
| +-- OgcWms (wms.ts)
| +-- VectorTiles
| +-- XyzTiles
+-- AbstractGeoViewVector
+-- CSV
+-- EsriFeature
+-- GeoJSON
+-- KML
+-- OgcFeature
+-- OgcWfs (wfs.ts)
+-- WKB
Note:
GeoPackageandshapefileare input file formats that are automatically converted to one of the vector types above (typically GeoJSON or WKB) during loading, so they don't have dedicated class implementations.
First, determine if your new layer type is raster or vector based on the OpenLayers source:
Raster Sources:
ImageStatic,ImageWMS,ImageArcGISRestTileWMS,XYZ,OSM- Extends
ImageSourceorTileSource
Vector Sources:
Vector,VectorTileGeoJSON,KML,GPXWKB(Well-Known Binary)CSV(with coordinates)- Extends
VectorSource
Note on Vector Tiles:
Despite being in the raster folder, VectorTiles represents pre-tiled vector data (Mapbox Vector Tiles/MVT format) and is different from raster XYZ tiles. It's organized under raster due to its tile-based delivery mechanism.
Create your new layer class in the appropriate category folder:
- Raster:
packages/geoview-core/src/geo/layer/geoview-layers/raster/ - Vector:
packages/geoview-core/src/geo/layer/geoview-layers/vector/
// packages/geoview-core/src/geo/layer/geoview-layers/raster/image-static.ts
import { AbstractGeoViewRaster } from "./abstract-geoview-raster";
import type {
TypeImageStaticLayerConfig,
TypeImageStaticLayerEntryConfig,
} from "@/geo/map/map-schema-types";
/**
* A class to add image static layer.
*
* @exports
* @class ImageStatic
*/
export class ImageStatic extends AbstractGeoViewRaster {
// Implementation here
}Create type guard functions to validate layer types:
export const layerConfigIsImageStatic = (
verifyIfLayer: TypeGeoviewLayerConfig
): verifyIfLayer is TypeImageStaticLayerConfig => {
return verifyIfLayer?.geoviewLayerType === CONST_LAYER_TYPES.IMAGE_STATIC;
};
export const geoviewLayerIsImageStatic = (
verifyIfGeoViewLayer: AbstractGeoViewLayer
): verifyIfGeoViewLayer is ImageStatic => {
return verifyIfGeoViewLayer?.type === CONST_LAYER_TYPES.IMAGE_STATIC;
};
export const geoviewEntryIsImageStatic = (
verifyIfGeoViewEntry: TypeLayerEntryConfig
): verifyIfGeoViewEntry is TypeImageStaticLayerEntryConfig => {
return (
verifyIfGeoViewEntry?.geoviewRootLayer?.geoviewLayerType ===
CONST_LAYER_TYPES.IMAGE_STATIC
);
};Create configuration types for your layer:
export interface TypeImageStaticLayerEntryConfig
extends Omit<TypeImageLayerEntryConfig, "source"> {
source: TypeSourceImageStaticInitialConfig;
}
export interface TypeImageStaticLayerConfig
extends Omit<TypeGeoviewLayerConfig, "listOfLayerEntryConfig"> {
geoviewLayerType: "imageStatic";
listOfLayerEntryConfig: TypeImageStaticLayerEntryConfig[];
}Add your source type to packages/geoview-core/src/geo/map/map-schema-types.ts:
/**
* Initial settings for image sources.
*/
export type TypeSourceImageInitialConfig =
| TypeSourceImageWmsInitialConfig
| TypeSourceImageEsriInitialConfig
| TypeSourceImageStaticInitialConfig; // Add your new type
/**
* Initial settings for static image sources.
*/
export interface TypeSourceImageStaticInitialConfig
extends TypeBaseSourceImageInitialConfig {
/** Image extent */
extent: Extent;
}Update packages/geoview-core/src/geo/layer/geoview-layers/abstract-geoview-layers.ts:
export const DEFAULT_LAYER_NAMES: string = {
// ... existing entries
imageStatic: "Static Image",
};export type LayerTypeKey =
| "esriDynamic"
| "esriFeature"
| "imageStatic" // Add here
| "GeoJSON";
// ... other typesexport type TypeGeoviewLayerType =
| "esriDynamic"
| "esriFeature"
| "imageStatic" // Add here
| "GeoJSON";
// ... other typesexport const CONST_LAYER_TYPES: {
[key in LayerTypeKey]: TypeGeoviewLayerType;
} = {
// ... existing entries
IMAGE_STATIC: "imageStatic",
};Implement all required abstract methods from parent classes:
protected abstract fetchServiceMetadata(): Promise<void>;
protected abstract validateListOfLayerEntryConfig(
listOfLayerEntryConfig: TypeListOfLayerEntryConfig
): TypeListOfLayerEntryConfig;
protected abstract processLayerMetadata(layerConfig: TypeLayerEntryConfig): Promise<void>;
protected abstract processOneLayerEntry(layerConfig: AbstractBaseLayerEntryConfig): Promise<BaseLayer | null>;
protected abstract getFeatureInfoAtPixel(location: Pixel, layerPath: string): Promise<TypeArrayOfFeatureInfoEntries>;
protected abstract getFeatureInfoAtCoordinate(location: Coordinate, layerPath: string): Promise<TypeArrayOfFeatureInfoEntries>;
protected abstract getFeatureInfoAtLonLat(location: Coordinate, layerPath: string): Promise<TypeArrayOfFeatureInfoEntries>;
protected abstract getFeatureInfoUsingBBox(location: Coordinate[], layerPath: string): Promise<TypeArrayOfFeatureInfoEntries>;
protected abstract getFeatureInfoUsingPolygon(location: Coordinate[], layerPath: string): Promise<TypeArrayOfFeatureInfoEntries>;
protected abstract getFieldDomain(fieldName: string, layerConfig: TypeLayerEntryConfig): null | codedValueType | rangeDomainType;
protected abstract getFieldType(fieldName: string, layerConfig: TypeLayerEntryConfig): 'string' | 'date' | 'number';export class ImageStatic extends AbstractGeoViewRaster {
/**
* Fetch service metadata (not needed for static images)
*/
protected async fetchServiceMetadata(): Promise<void> {
// Static images don't have service metadata
return Promise.resolve();
}
/**
* Validate layer entry configuration
*/
protected validateListOfLayerEntryConfig(
listOfLayerEntryConfig: TypeListOfLayerEntryConfig
): TypeListOfLayerEntryConfig {
// Validate and return config
return listOfLayerEntryConfig;
}
/**
* Process layer metadata
*/
protected async processLayerMetadata(
layerConfig: TypeLayerEntryConfig
): Promise<void> {
// Process metadata for this layer entry
}
/**
* Create OpenLayers layer
*/
protected async processOneLayerEntry(
layerConfig: AbstractBaseLayerEntryConfig
): Promise<BaseLayer | null> {
const olLayer = new ImageLayer({
source: new Static({
url: layerConfig.source.dataAccessPath,
projection: layerConfig.source.projection,
imageExtent: layerConfig.source.extent,
}),
});
return olLayer;
}
// Implement other required methods...
}Add your layer type to the loading process in packages/geoview-core/src/geo/layer/layer.ts:
import {
ImageStatic,
layerConfigIsImageStatic,
} from "./geoview-layers/raster/image-static";
// In the EVENT_ADD_LAYER handler:
if (layerConfigIsImageStatic(layerConfig)) {
const imageStatic = new ImageStatic(this.mapId, layerConfig);
imageStatic.createGeoViewLayers().then(() => {
this.addToMap(imageStatic);
});
}Update packages/geoview-core/schema.json to include your new layer type:
"TypeGeoviewLayerType": {
"type": "string",
"items": {
"enum": [
"esriDynamic",
"esriFeature",
"imageStatic",
"GeoJSON",
"geoCore",
"GeoPackage",
"xyzTiles",
"ogcFeature",
"ogcWfs",
"ogcWms"
]
},
"description": "Type of GeoView layer."
}"TypeSourceImageStaticInitialConfig": {
"type": "object",
"properties": {
"extent": {
"type": "array",
"items": { "type": "number" },
"minItems": 4,
"maxItems": 4,
"description": "Image extent [minX, minY, maxX, maxY]"
},
"projection": {
"type": "string",
"description": "Image projection code"
}
},
"required": ["extent"]
}"TypeImageStaticLayerConfig": {
"allOf": [
{ "$ref": "#/definitions/TypeGeoviewLayerConfig" },
{
"properties": {
"geoviewLayerType": {
"const": "imageStatic"
},
"listOfLayerEntryConfig": {
"type": "array",
"items": { "$ref": "#/definitions/TypeImageStaticLayerEntryConfig" }
}
}
}
]
}Export your new types and classes from the appropriate index files:
export * from "./image-static";export type { TypeImageStaticLayerConfig, TypeImageStaticLayerEntryConfig };
export type { TypeSourceImageStaticInitialConfig };Create unit tests in __tests__ folder:
import {
ImageStatic,
layerConfigIsImageStatic,
} from "@/geo/layer/geoview-layers/raster/image-static";
describe("ImageStatic Layer", () => {
it("should validate layer config correctly", () => {
const config = {
geoviewLayerId: "test",
geoviewLayerType: "imageStatic",
// ... other config
};
expect(layerConfigIsImageStatic(config)).toBe(true);
});
it("should create layer successfully", async () => {
const imageStatic = new ImageStatic("mapId", validConfig);
await imageStatic.createGeoViewLayers();
expect(imageStatic.olLayers.length).toBeGreaterThan(0);
});
});Test in a real map:
const mapViewer = cgpv.api.createMapFromConfig("mapId", {
map: {
// ... map config
},
layers: [
{
geoviewLayerId: "testImageStatic",
geoviewLayerType: "imageStatic",
metadataAccessPath: "https://example.com/image.png",
listOfLayerEntryConfig: [
{
layerId: "image",
source: {
extent: [-180, -90, 180, 90],
projection: "EPSG:4326",
},
},
],
},
],
});
mapViewer.layer.onLayerFirstLoaded((sender, payload) => {
console.log("Layer loaded successfully:", payload.layer.getLayerPath());
});protected async fetchServiceMetadata(): Promise<void> {
const metadataUrl = this.metadata.metadataAccessPath[this.mapId];
try {
const response = await fetch(metadataUrl);
const metadata = await response.json();
// Store metadata for use in other methods
this.serviceMetadata = metadata;
} catch (error) {
logger.logError('Failed to fetch metadata:', error);
throw error;
}
}protected async getFeatureInfoAtPixel(
location: Pixel,
layerPath: string
): Promise<TypeArrayOfFeatureInfoEntries> {
const coordinate = this.map.getCoordinateFromPixel(location);
return this.getFeatureInfoAtCoordinate(coordinate, layerPath);
}
protected async getFeatureInfoAtCoordinate(
location: Coordinate,
layerPath: string
): Promise<TypeArrayOfFeatureInfoEntries> {
// Implement GetFeatureInfo request
const url = this.buildGetFeatureInfoUrl(location, layerPath);
const response = await fetch(url);
const data = await response.json();
return this.parseFeatureInfo(data);
}protected applyStyle(olLayer: BaseLayer, styleConfig: any): void {
if (olLayer instanceof VectorLayer) {
const style = new Style({
fill: new Fill({ color: styleConfig.fillColor }),
stroke: new Stroke({ color: styleConfig.strokeColor, width: styleConfig.strokeWidth }),
});
olLayer.setStyle(style);
}
}- Extend the correct abstract class (Raster vs Vector)
- Implement all abstract methods completely
- Use type guards for type checking
- Handle errors gracefully with try/catch and logging
- Add comprehensive tests for your layer type
- Document your layer type in user-facing docs
- Follow naming conventions (e.g.,
ImageStatic, notStaticImage)
- Don't modify abstract classes unless necessary
- Don't bypass validation - use schema validation
- Don't hardcode values - use configuration
- Don't forget to export types and classes
- Don't skip error handling in async methods
- Check console for validation errors
- Verify layer type is registered in
CONST_LAYER_TYPES - Ensure type guard functions are correct
- Check if layer is added to loading switch in
layer.ts
- Verify all types are exported from
map-schema-types.ts - Check type guard function signatures
- Ensure source type is added to union type
- Verify
schema.jsonincludes your layer type - Check all required properties are defined
- Test configuration against schema
- GeoView Layers Guide - User-facing layer documentation
- Layer API Reference - API methods
- TypeScript Patterns - TypeScript conventions
- Best Practices - General coding standards
See existing layer implementations for complete examples:
- Raster:
packages/geoview-core/src/geo/layer/geoview-layers/raster/ogc-wms.ts - Vector:
packages/geoview-core/src/geo/layer/geoview-layers/vector/geojson.ts