'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 };