UNPKG

@dtcv/citymodel

Version:
385 lines 12.6 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { vec3, mat4 } from 'gl-matrix'; // import { generateColor } from '@dtcv/indicators'; import { convert } from '@dtcv/convert'; import * as protobuf from 'protobufjs'; const isProd = process.env.NODE_ENV === 'production'; let protoRoot; function initPb() { return __awaiter(this, void 0, void 0, function* () { protoRoot = yield protobuf.load(isProd ? '/dtcc.proto' : 'https://digitaltwincityviewer.s3.eu-north-1.amazonaws.com/dtcc.proto'); }); } initPb(); function parseGround(fileData, fromCrs, toCrs, center) { const groundSurface = fileData.GroundSurface || fileData.groundSurface; const origin = fileData.Origin || fileData.origin || { x: 0, y: 0 }; const vertices = groundSurface ? groundSurface.vertices : fileData.vertices; const indices = groundSurface ? groundSurface.faces : fileData.faces; if (!vertices) { return null; } let minX = Infinity; let minY = Infinity; let maxX = -Infinity; let maxY = -Infinity; const projectedVertices = []; for (let i = 0; i < vertices.length; i++) { const { x = 0, y = 0, z = 0 } = vertices[i]; const projected = convert({ x: x + origin.x, y: y + origin.y, fromCrs, toCrs, center, }); projected.push(z); if (projected[0] < minX) { minX = projected[0]; } if (projected[1] < minY) { minY = projected[1]; } if (projected[0] > maxX) { maxX = projected[0]; } if (projected[1] > maxY) { maxY = projected[1]; } // todo: adjust for altitude in espg:3857 projectedVertices.push(...projected); } const bounds = [minX, minY, 0, maxX, maxY, 0]; const layerPosition = getLayerPosition(bounds); const flatIndices = indices.reduce((acc, i) => { if (typeof i === 'number') { acc.push(i); } const { v0 = 0, v1 = 0, v2 = 0 } = i; acc.push(v0, v1, v2); return acc; }, []); return Object.assign(Object.assign({}, layerPosition), { origin, bounds, data: { indices: flatIndices, vertices: projectedVertices, } }); } function getModelMatrix(bounds) { const min = bounds[0]; const max = bounds[1]; const size = vec3.sub(vec3.create(), max, min); const offset = vec3.add(vec3.create(), min, vec3.scale(vec3.create(), size, 0.5)); const position = vec3.negate(vec3.create(), offset); const modelMatrix = mat4.fromTranslation(mat4.create(), position); return Array.from(modelMatrix); } function getLayerPosition(extent) { const min = [extent[0], extent[1], extent[2]]; const max = [extent[3], extent[4], extent[5]]; const modelMatrix = getModelMatrix([min, max]); const size = vec3.sub(vec3.create(), max, min); const center = vec3.add(vec3.create(), min, vec3.scale(vec3.create(), size, 0.5)); return { min, max, width: size[0], height: size[1], center: Array.from(center), modelMatrix, }; } // Only for lod 1 so far function parseBuildings(fileData, fromCrs, toCrs, center, setZToZero = false) { const buildings = fileData.Buildings || fileData.buildings; // todo: the crs is discussed to be added to CityModel files, so add that when it comes in new examples const origin = fileData.Origin || fileData.origin || { x: 0, y: 0 }; if (!buildings) { return null; } const features = []; let minX = Infinity; let minY = Infinity; let maxX = -Infinity; let maxY = -Infinity; for (let i = 0; i < buildings.length; i++) { const building = buildings[i]; const footprint = building.Footprint || building.footPrint; const coordinates = []; if (footprint.holes) { console.log(footprint); } const polygon = footprint.shell ? footprint.shell.vertices : footprint.vertices ? footprint.vertices : footprint; for (let j = 0; j < polygon.length; j++) { const { x, y } = polygon[j]; const projected = convert({ x: x + origin.x, y: y + origin.y, fromCrs, toCrs, center, }); if (projected[0] < minX) { minX = projected[0]; } if (projected[1] < minY) { minY = projected[1]; } if (projected[0] > maxX) { maxX = projected[0]; } if (projected[1] > maxY) { maxY = projected[1]; } coordinates.push([ projected[0], projected[1], setZToZero ? 0 : building.GroundHeight || building.groundHeight, ]); } if (coordinates[0]) { coordinates.push([...coordinates[0]]); } const feature = { id: null, type: 'Feature', properties: { type: 'building', uuid: building.UUID || building.uuid, shpFileId: building.SHPFileID, elevation: building.Height || building.height, groundHeight: building.GroundHeight || building.groundHeight, height: building.Height || building.height, }, geometry: { type: 'Polygon', coordinates: [coordinates], }, }; if (building.id) { feature.id = building.id; } if (building.attributes) { Object.assign(feature.properties, building.attributes); } features.push(feature); } const bounds = [minX, minY, 0, maxX, maxY, 0]; const layerPosition = getLayerPosition(bounds); return Object.assign(Object.assign({}, layerPosition), { data: features, origin, center }); } function parseSurfaceField(fileData) { if (!fileData.surface || !fileData.values) { return null; } const origin = fileData.Origin || fileData.origin || { x: 0, y: 0 }; const vertices = fileData.surface.vertices; const indices = fileData.surface.faces; const values = fileData.values; if (!vertices) { return null; } let minX = Infinity; let minY = Infinity; let maxX = -Infinity; let maxY = -Infinity; const projectedVertices = []; for (let i = 0; i < vertices.length; i++) { const v = vertices[i]; const projected = [v.x || 0, v.y || 0, v.z || 0]; // const transformed = transformCoordinate(vertices[i], vertices[i + 1], { // translate: [origin.x, origin.y], // }); // const projected = projectCoordinate(transformed[0], transformed[1]); if (projected[0] < minX) { minX = projected[0]; } if (projected[1] < minY) { minY = projected[1]; } if (projected[0] > maxX) { maxX = projected[0]; } if (projected[1] > maxY) { maxY = projected[1]; } // todo: adjust for altitude in espg:3857 projectedVertices.push(...projected); } const bounds = [minX, minY, 0, maxX, maxY, 0]; const { modelMatrix, center, min, max, width, height } = getLayerPosition(bounds); const colors = []; let minVal = Infinity; let maxVal = -Infinity; for (let i = 0; i < values.length; i++) { if (values[i] < minVal) { minVal = values[i]; } if (values[i] > maxVal) { maxVal = values[i]; } } for (let i = 0; i < values.length; i++) { //const color = generateColor(values[i], 4, 2); // const color = generateColor( // values[i], // minVal, // maxVal, // 'red', // 'blue', // 'orange' // ); // todo: bring back the generateColor function const color = [0.5, 0.5, 0.5]; colors.push(color[0] / 255, color[1] / 255, color[2] / 255, 0.6); } //console.log(minVal, maxVal); const flatIndices = indices.reduce((acc, i) => { if (typeof i === 'number') { acc.push(i); } if (i.v0) { acc.push(i.v0); } else if (i.v1) { acc.push(0); } if (i.v1) { acc.push(i.v1); } if (i.v2) { acc.push(i.v2); } return acc; }, []); return { origin, bounds, modelMatrix, center, min, max, width, height, data: { indices: flatIndices, vertices: projectedVertices, colors, }, }; } function getPointCloudColor(classification) { // 19-63 is reserved, 64-255 ia user definable const colors = [ [196, 188, 196], [196, 188, 196], [124, 124, 116], [58, 68, 57], [58, 68, 57], [58, 68, 57], [58, 68, 57], [58, 68, 57], [58, 68, 57], [58, 68, 57], [58, 68, 57], [58, 68, 57], [58, 68, 57], [58, 68, 57], [58, 68, 57], [58, 68, 57], [58, 68, 57], [58, 68, 57], [58, 68, 57], // 18: high noise ]; return colors[classification] || [63, 191, 63]; } function parsePointCloud(fileData, fromCrs) { const points = []; const origin = fileData.Origin || fileData.origin || { x: 0, y: 0 }; console.log(origin); //const selection = fileData.points.slice(0, 1000000); let i = 0; const classes = {}; for (const p of fileData.points) { const classification = fileData.classification[i]; classes[classification] = true; i++; if (!p.x || !p.y) { continue; } const projected = convert({ x: p.x + origin.x, y: p.y + origin.y, fromCrs, }); points.push({ position: [projected[0], projected[1], p.z], color: getPointCloudColor(classification), normal: [-1, 0, 0], }); } console.log(classes); return { data: points, }; } // old name, should be DTCC model? Since CityModel is just one part of DTCC model function parseCityModel(options) { const result = { // this can be overriden if necessary from the parsers modelMatrix: mat4.create(), }; const { data: fileData, type, fromCrs, toCrs, center, setZToZero } = options; if (type === 'CityModel') { const buildings = parseBuildings(fileData, fromCrs, toCrs, center, setZToZero); if (buildings) { result.buildings = buildings; } } else if (type === 'Surface3D') { const ground = parseGround(fileData, fromCrs, toCrs, center); if (ground) { result.ground = ground; } } else if (type === 'SurfaceField3D') { const surfaceField = parseSurfaceField(fileData); if (surfaceField) { result.surfaceField = surfaceField; } } else if (type === 'PointCloud') { const pointCloud = parsePointCloud(fileData, fromCrs); if (pointCloud) { result.pointCloud = pointCloud; } } return result; } function parseProtobuf(pbData, pbType) { if (!protoRoot) { console.warn('protobuf has not been loaded properly during init'); return; } const typeData = protoRoot.lookupType(pbType); const decoded = typeData.decode(pbData); const decodedJson = decoded.toJSON(); return decodedJson; } export { parseCityModel, parseProtobuf }; //# sourceMappingURL=index.js.map