Skip to content

Commit

Permalink
Merge pull request #1659 from concord-consortium/188620026-fix-v2-map…
Browse files Browse the repository at this point in the history
…-importer

fix: import of v2 documents with maps
  • Loading branch information
kswenson authored Dec 2, 2024
2 parents acbae57 + 6d86b2d commit dc907ff
Show file tree
Hide file tree
Showing 7 changed files with 18,939 additions and 38 deletions.
7 changes: 7 additions & 0 deletions v3/src/components/map/map-model-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ export const LatLngModel = types.model("LatLngModel", {
lat: 0,
lng: 0
})
.preProcessSnapshot(snap => {
if (Array.isArray(snap)) {
const [lat, lng] = snap
return { lat, lng }
}
return snap
})
export interface ILatLngModel extends Instance<typeof LatLngModel> {}
export interface ILatLngSnapshot extends SnapshotOut<typeof LatLngModel> {}

Expand Down
28 changes: 14 additions & 14 deletions v3/src/components/map/models/map-content-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
import {GraphPlace} from '../../axis-graph-shared'
import {DataDisplayContentModel} from "../../data-display/models/data-display-content-model"
import {isMapPolygonLayerModel, MapPolygonLayerModel} from "./map-polygon-layer-model"
import {MapPointLayerModel} from "./map-point-layer-model"
import {isMapPointLayerModel, MapPointLayerModel} from "./map-point-layer-model"
import {ILatLngSnapshot, LatLngModel} from '../map-model-types'
import {LeafletMapState} from './leaflet-map-state'
import {isMapLayerModel} from "./map-layer-model"
Expand Down Expand Up @@ -225,24 +225,24 @@ export const MapContentModel = DataDisplayContentModel
const layersToCheck = Array.from(self.layers)
sharedDataSets.forEach(sharedDataSet => {
if (datasetHasLatLongData(sharedDataSet.dataSet)) {
const foundIndex = layersToCheck.findIndex(aLayer => aLayer.data === sharedDataSet.dataSet)
if (foundIndex >= 0) {
const layer = layersToCheck[foundIndex],
dataset = sharedDataSet.dataSet
layer.dataConfiguration.setDataset(dataset, getSharedCaseMetadataFromDataset(dataset))
// Remove this layer from the list of layers to check since it _is_ present
layersToCheck.splice(foundIndex, 1)
const pointLayer = layersToCheck.find(layer => {
return layer.data === sharedDataSet.dataSet && isMapPointLayerModel(layer)
})
if (isMapPointLayerModel(pointLayer)) {
pointLayer.setDataset(sharedDataSet.dataSet)
layersToCheck.splice(layersToCheck.indexOf(pointLayer), 1)
} else {
// Add a new layer for this dataset
this.addPointLayer(sharedDataSet.dataSet)
}
}
// Todo: We should allow both points and polygons from the same dataset
else if (datasetHasBoundaryData(sharedDataSet.dataSet)) {
const layer = layersToCheck.find(aLayer => aLayer.data === sharedDataSet.dataSet)
if (layer && isMapPolygonLayerModel(layer)) {
layer.setDataset(sharedDataSet.dataSet)
layersToCheck.splice(layersToCheck.indexOf(layer), 1)
if (datasetHasBoundaryData(sharedDataSet.dataSet)) {
const polygonLayer = layersToCheck.find(layer => {
return layer.data === sharedDataSet.dataSet && isMapPolygonLayerModel(layer)
})
if (isMapPolygonLayerModel(polygonLayer)) {
polygonLayer.setDataset(sharedDataSet.dataSet)
layersToCheck.splice(layersToCheck.indexOf(polygonLayer), 1)
} else {
// Add a new layer for this dataset
this.addPolygonLayer(sharedDataSet.dataSet)
Expand Down
119 changes: 119 additions & 0 deletions v3/src/components/map/v2-map-importer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { DocumentContentModel, IDocumentContentModel } from "../../models/document/document-content"
import { FreeTileRow } from "../../models/document/free-tile-row"
import { SharedModelDocumentManager } from "../../models/document/shared-model-document-manager"
import { ITileModelSnapshotIn } from "../../models/tiles/tile-model"
import { safeJsonParse } from "../../utilities/js-utils"
import { CodapV2Document } from "../../v2/codap-v2-document"
import { ICodapV2DocumentJson } from "../../v2/codap-v2-types"
import { isMapContentModel } from "./models/map-content-model"
import { v2MapImporter } from "./v2-map-importer"
import "./map-registration"

const fs = require("fs")
const path = require("path")

function firstMapComponent(v2Document: CodapV2Document) {
return v2Document.components.find(c => c.type === "DG.MapView")!
}

describe("V2MapImporter imports legacy v2 map documents", () => {
// legacy v2 map document (0252)
const sealAndSharkFile = path.join(__dirname, "../../test/v2", "seal-and-shark-demo.codap")
const sealAndSharkJson = fs.readFileSync(sealAndSharkFile, "utf8")
const sealAndSharkDoc = safeJsonParse<ICodapV2DocumentJson>(sealAndSharkJson)!

let v2Document: CodapV2Document
let docContent: Maybe<IDocumentContentModel>
let sharedModelManager: Maybe<SharedModelDocumentManager>
const mockInsertTile = jest.fn((tileSnap: ITileModelSnapshotIn) => {
const tile = docContent!.insertTileSnapshotInDefaultRow(tileSnap)
return tile
})

beforeEach(() => {
v2Document = new CodapV2Document(sealAndSharkDoc)
sharedModelManager = new SharedModelDocumentManager()
docContent = DocumentContentModel.create({}, { sharedModelManager })
docContent.setRowCreator(() => FreeTileRow.create())
sharedModelManager.setDocument(docContent)

// load shared models into sharedModelManager
v2Document.contexts.forEach(({ guid }) => {
const { data, metadata } = v2Document.getDataAndMetadata(guid)
data && sharedModelManager!.addSharedModel(data)
metadata?.setData(data?.dataSet)
metadata && sharedModelManager!.addSharedModel(metadata)
})

mockInsertTile.mockClear()
})

it("imports legacy v2 map components", () => {
const tile = v2MapImporter({
v2Component: firstMapComponent(v2Document),
v2Document,
insertTile: mockInsertTile
})
expect(mockInsertTile).toHaveBeenCalledTimes(1)
const mapModel = isMapContentModel(tile?.content) ? tile?.content : undefined
expect(mapModel).toBeDefined()
// layers aren't actually imported from legacy documents
expect(mapModel?.layers.length).toBe(0)
})
})

describe("V2MapImporter imports current v2 map documents", () => {
// current v2 map document (0730)
const rollerCoastersFile = path.join(__dirname, "../../test/v2", "roller-coasters-map.codap")
const rollerCoastersJson = fs.readFileSync(rollerCoastersFile, "utf8")
const rollerCoastersDoc = safeJsonParse<ICodapV2DocumentJson>(rollerCoastersJson)!

let v2Document: CodapV2Document
let docContent: Maybe<IDocumentContentModel>
let sharedModelManager: Maybe<SharedModelDocumentManager>
const mockInsertTile = jest.fn((tileSnap: ITileModelSnapshotIn) => {
const tile = docContent!.insertTileSnapshotInDefaultRow(tileSnap)
return tile
})

beforeEach(() => {
v2Document = new CodapV2Document(rollerCoastersDoc)
sharedModelManager = new SharedModelDocumentManager()
docContent = DocumentContentModel.create({}, { sharedModelManager })
docContent.setRowCreator(() => FreeTileRow.create())
sharedModelManager.setDocument(docContent)

// load shared models into sharedModelManager
v2Document.contexts.forEach(({ guid }) => {
const { data, metadata } = v2Document.getDataAndMetadata(guid)
data && sharedModelManager!.addSharedModel(data)
metadata?.setData(data?.dataSet)
metadata && sharedModelManager!.addSharedModel(metadata)
})

mockInsertTile.mockClear()
})

it("handles empty components", () => {
const noTile = v2MapImporter({
v2Component: {} as any,
v2Document,
insertTile: mockInsertTile
})
expect(mockInsertTile).toHaveBeenCalledTimes(0)
const mapModel = isMapContentModel(noTile?.content) ? noTile?.content : undefined
expect(mapModel).not.toBeDefined()
})

it("imports current v2 map components", () => {
const tile = v2MapImporter({
v2Component: firstMapComponent(v2Document),
v2Document,
insertTile: mockInsertTile
})
expect(mockInsertTile).toHaveBeenCalledTimes(1)
const mapModel = isMapContentModel(tile?.content) ? tile?.content : undefined
expect(mapModel).toBeDefined()
expect(mapModel?.layers.length).toBe(2)
})
})
25 changes: 16 additions & 9 deletions v3/src/components/map/v2-map-importer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import {ITileModelSnapshotIn} from "../../models/tiles/tile-model"
import {toV3Id} from "../../utilities/codap-utils"
import {V2TileImportArgs} from "../../v2/codap-v2-tile-importers"
import {isV2MapComponent, v3TypeFromV2TypeIndex} from "../../v2/codap-v2-types"
import {
isV2MapComponent, isV2MapCurrentStorage, isV2MapPointLayerStorage, isV2MapPolygonLayerStorage,
v3TypeFromV2TypeIndex
} from "../../v2/codap-v2-types"
import {AttrRole} from "../data-display/data-display-types"
import {IAttributeDescriptionSnapshot, kDataConfigurationType} from "../data-display/models/data-configuration-model"
import {IMapModelContentSnapshot} from "./models/map-content-model"
Expand All @@ -16,14 +19,20 @@ export function v2MapImporter({v2Component, v2Document, insertTile}: V2TileImpor
if (!isV2MapComponent(v2Component)) return

const { guid, componentStorage: { name, title = "", mapModelStorage } } = v2Component
const { center, zoom, baseMapLayerName: v2BaseMapLayerName,
layerModels: v2LayerModels } = mapModelStorage
const { center, zoom, baseMapLayerName: v2BaseMapLayerName } = mapModelStorage
const baseMapKeyMap: Record<string, BaseMapKey> = { Topographic: 'topo', Streets: 'streets', Oceans: 'oceans' }
const baseMapLayerName = baseMapKeyMap[v2BaseMapLayerName]

const layers: Array<IMapBaseLayerModelSnapshot | IMapPolygonLayerModelSnapshot | IMapPointLayerModelSnapshot> = []

v2LayerModels.forEach((v2LayerModel, layerIndex) => {
// Note that v2 associates layers with collections automatically, rather than based on what is imported/restored.
// The imported/restored contents just modify the display properties of the pre-assigned layers, rather than
// determining the structure of the layers as this code implies.

const v2LayerModels = isV2MapCurrentStorage(v2Component.componentStorage)
? v2Component.componentStorage.mapModelStorage.layerModels
: []
v2LayerModels?.forEach((v2LayerModel, layerIndex) => {
// Pull out stuff from _links_ and decide if it's a point layer or polygon layer
const contextId = v2LayerModel._links_.context.id,
_attributeDescriptions: Partial<Record<AttrRole, IAttributeDescriptionSnapshot>> = {},
Expand All @@ -34,7 +43,6 @@ export function v2MapImporter({v2Component, v2Document, insertTile}: V2TileImpor
legendAttributeId = v2LegendAttribute?.id,
legendAttribute = legendAttributeId ? v2Document.getV3Attribute(legendAttributeId) : undefined,
v3LegendAttrId = legendAttribute?.id ?? '',
isPoints = v2LayerModel.pointColor !== undefined,
{data, metadata} = v2Document.getDataAndMetadata(contextId),
{
isVisible, legendAttributeType, strokeSameAsFill,
Expand All @@ -52,7 +60,7 @@ export function v2MapImporter({v2Component, v2Document, insertTile}: V2TileImpor
}
}

if (isPoints) {
if (isV2MapPointLayerStorage(v2LayerModel)) {
const {
pointColor, strokeColor, pointSizeMultiplier,
grid, pointsShouldBeVisible, connectingLines
Expand Down Expand Up @@ -89,11 +97,11 @@ export function v2MapImporter({v2Component, v2Document, insertTile}: V2TileImpor
}
layers.push(pointLayerSnapshot)
}
else {
else if (isV2MapPolygonLayerStorage(v2LayerModel)) {
const {
areaColor, areaStrokeColor,
/* Present in v2 layer model but not yet used in V3 layer model:
areaTransparency, strokeTransparency, areaStrokeTransparency
areaTransparency, areaStrokeTransparency
*/
} = v2LayerModel
// V2 polygon layers don't store their boundary attribute, so we need to find it in the dataset
Expand All @@ -114,7 +122,6 @@ export function v2MapImporter({v2Component, v2Document, insertTile}: V2TileImpor
_itemStrokeSameAsFill: strokeSameAsFill,
/*
areaTransparency,
strokeTransparency,
areaStrokeTransparency
*/
}
Expand Down
Loading

0 comments on commit dc907ff

Please sign in to comment.