'use strict'; const assert = { strictEqual(actual, expected, ...args) { args = args || []; if (actual !== expected) { throw new Error(`${actual} (actual) should equal ${expected} (expected): ${[...args].join(' ')}`); } }, notStrictEqual(actual, expected, ...args) { args = args || []; if (actual === expected) { throw new Error(`${actual} (actual) should NOT equal ${expected} (expected): ${[...args].join(' ')}`); } }, }; /* function dumpBuf(buf) { for (let i = 0; i < buf.length; i += 32) { const p = []; const a = []; for (let j = i; j < i + 32 && j < buf.length; ++j) { const b = buf[j]; p.push(b.toString(16).padStart(2, '0')); a.push(b >= 32 && b < 128 ? String.fromCharCode(b) : '.'); if (j % 4 === 3) { p.push(' '); } } console.log(i.toString(16).padStart(8, '0'), ':', p.join(''), a.join('')); } } */ function parse(buf) { assert.strictEqual(buf[0], 0x47, 'bad header'); assert.strictEqual(buf[1], 0x50, 'bad header'); assert.strictEqual(buf[2], 0, 'unknown version'); // version const flags = buf[3]; const flag_x = (flags >> 5) & 1; // const flag_empty_geo = (flags >> 4) & 1; // 1 = empty, 0 non-empty const flag_byteOrder = (flags >> 0) & 1; // 1 = little endian, 0 = big const flag_envelope = (flags >> 1) & 7; assert.strictEqual(flag_x, 0, 'x must be 0'); const envelopeSizes = [ 0, // 0: non 4, // 1: minx, maxx, miny, maxy 6, // 2: minx, maxx, miny, maxy, minz, maxz 6, // 3: minx, maxx, miny, maxy, minm, maxm 8, // 4: minx, maxx, miny, maxy, minz, maxz, minm, maxm ]; const envelopeSize = envelopeSizes[flag_envelope]; assert.notStrictEqual(envelopeSize, undefined); const headerSize = 8; let cursor = headerSize; const dataView = new DataView(buf.buffer); /* const readBE = { getDouble() { const v = buf.readDoubleBE(cursor); cursor += 8 ; return v; }, getFloat() { const v = buf.readFloatBE(cursor); cursor += 4 ; return v; }, getInt8() { const v = buf.readInt8(cursor); cursor += 1 ; return v; }, getUint8() { const v = buf.readUInt8(cursor); cursor += 1 ; return v; }, getInt16() { const v = buf.readInt16BE(cursor); cursor += 2 ; return v; }, getUint16() { const v = buf.readUInt16BE(cursor); cursor += 2 ; return v; }, getInt32() { const v = buf.readInt32BE(cursor); cursor += 4 ; return v; }, getUint32() { const v = buf.readUInt32BE(cursor); cursor += 4 ; return v; }, }; const readLE = { getDouble() { const v = buf.readDoubleLE(cursor); cursor += 8 ; return v; }, getFloat() { const v = buf.readFloatLE(cursor); cursor += 4 ; return v; }, getInt8() { const v = buf.readInt8(cursor); cursor += 1 ; return v; }, getUint8() { const v = buf.readUInt8(cursor); cursor += 1 ; return v; }, getInt16() { const v = buf.readInt16LE(cursor); cursor += 2 ; return v; }, getUint16() { const v = buf.readUInt16LE(cursor); cursor += 2 ; return v; }, getInt32() { const v = buf.readInt32LE(cursor); cursor += 4 ; return v; }, getUint32() { const v = buf.readUInt32LE(cursor); cursor += 4 ; return v; }, }; */ let littleEndian; const endianStack = []; function pushByteOrder(byteOrder) { endianStack.push(littleEndian); littleEndian = byteOrder; } function popByteOrder() { littleEndian = endianStack.pop(); } const getDouble = () => { const v = dataView.getFloat64(cursor, littleEndian); cursor += 8 ; return v; }; // const getFloat = () => { const v = dataView.getFloat32(cursor, littleEndian); cursor += 4 ; return v; }; const getInt8 = () => { const v = dataView.getInt8(cursor); cursor += 1 ; return v; }; // const getUint8 = () => { const v = dataView.getUint8(cursor, littleEndian); cursor += 1 ; return v; }; // const getInt16 = () => { const v = dataView.getInt16(cursor, littleEndian); cursor += 2 ; return v; }; // const getUint16 = () => { const v = dataView.getUint16(cursor, littleEndian); cursor += 2 ; return v; }; // const getInt32 = () => { const v = dataView.getInt32(cursor, littleEndian); cursor += 4 ; return v; }; const getUint32 = () => { const v = dataView.getUint32(cursor, littleEndian); cursor += 4 ; return v; }; pushByteOrder(flag_byteOrder); const envelope = []; for (let i = 0; i < envelopeSize; ++i) { envelope.push(getDouble()); } const primitives = []; function getPoints(num) { const points = []; for (let i = 0; i < num; ++i) { points.push(getDouble(), getDouble()); } return points; } function getRings(num) { const rings = []; for (let i = 0; i < num; ++i) { rings.push(getPoints(getUint32())); } return rings; } function pointHandler() { return { type: 'point', point: getPoints(1), }; } function lineStringHandler() { return { type: 'lineString', points: getPoints(getUint32()), }; } function polygonHandler() { return { type: 'polygon', rings: getRings(getUint32()), }; } function multiPointHandler() { // WTF? const points = []; const num = getUint32(); for (let i = 0; i < num; ++i) { pushByteOrder(getInt8()); const type = getUint32(); assert.strictEqual(type, 1); // must be point points.push(getDouble(), getDouble()); popByteOrder(); } return { type: 'multiPoint', points, }; } function multiLineStringHandler() { // WTF? const lineStrings = []; const num = getUint32(); for (let i = 0; i < num; ++i) { pushByteOrder(getInt8()); const type = getUint32(); assert.strictEqual(type, 2); // must be lineString lineStrings.push(getPoints(getUint32())); popByteOrder(); } return { type: 'multiLineString', lineStrings, }; } function multiPolygonHandler() { // WTF? const polygons = []; const num = getUint32(); for (let i = 0; i < num; ++i) { pushByteOrder(getInt8()); const type = getUint32(); assert.strictEqual(type, 3); // must be polygon polygons.push(getRings(getUint32())); popByteOrder(); } return { type: 'multiPolygon', polygons, }; } const typeHandlers = [ undefined, // 0 pointHandler, // 1 lineStringHandler, // 2 polygonHandler, // 3 multiPointHandler, // 4 multiLineStringHandler, // 5, multiPolygonHandler, // 6, ]; const end = buf.length; while (cursor < end) { pushByteOrder(getInt8()); const type = getUint32(); const handler = typeHandlers[type]; assert.notStrictEqual(handler, undefined, 'unknown type'); primitives.push(handler()); popByteOrder(); } return { envelope, primitives, }; } window.ogcParser = {parse};