/******************************************************************************* * Copyright (c) 2014 Open Door Logistics (www.opendoorlogistics.com) * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser Public License 3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html * ******************************************************************************/ package com.opendoorlogistics.core.geometry.rog.builder; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.util.List; import com.fasterxml.jackson.core.JsonGenerator; import com.opendoorlogistics.codefromweb.jxmapviewer2.fork.swingx.mapviewer.TileFactoryInfo; import com.opendoorlogistics.core.geometry.rog.RogReaderUtils; import com.opendoorlogistics.core.geometry.rog.builder.QuadBlockBuilder.QuadBlock; import com.opendoorlogistics.core.geometry.rog.builder.ROGBuilder.PendingWrite; import com.opendoorlogistics.core.utils.LargeList; import de.undercouch.bson4jackson.BsonFactory; class QuadWriter { final private FileChannel tmpChannel; final private LargeList<Long> quadPositions = new LargeList<>(); final private File tmpFile; QuadWriter(File tmpFile) { try { this.tmpFile = tmpFile; if(tmpFile.exists()){ tmpFile.delete(); } @SuppressWarnings("resource") RandomAccessFile rf = new RandomAccessFile(tmpFile, "rw"); tmpChannel = rf.getChannel(); } catch (Exception e) { throw new RuntimeException(e); } } void finish(boolean isNOLPL, List<ShapeIndex> shapes,File outFile){ try { FileOutputStream outFos = new FileOutputStream(outFile); FileChannel outChannel = outFos.getChannel(); DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(Channels.newOutputStream(outChannel))); writeObjectIndex(isNOLPL,shapes, dos); writeQuadData(outChannel,dos); outFos.close(); tmpChannel.close(); tmpFile.delete(); } catch (Exception e) { throw new RuntimeException(e); } } long getTmpSizeBytes(){ try { return tmpChannel.position(); } catch (Exception e) { throw new RuntimeException(e); } } private void writeQuadData(FileChannel outChannel, DataOutputStream dos){ try { // calculate the start position of the quad data dos.flush(); long n = quadPositions.size(); long indexSizeBytes = 8 + n * 8; long quadDataStart = outChannel.position() + indexSizeBytes; // write the number of quad positions dos.writeLong(n); // write the index of quad positions for(long i=0; i < n ; i++){ dos.writeLong(quadDataStart + quadPositions.get(i)); } dos.flush(); // now write all quad data, copying from the temp file tmpChannel.position(0); BufferedInputStream bis = new BufferedInputStream(Channels.newInputStream(tmpChannel)); int nextByte = bis.read(); while(nextByte!=-1){ dos.write(nextByte); nextByte = bis.read(); } dos.flush(); } catch (Exception e) { throw new RuntimeException(e); } } void add(Iterable<PendingWrite> writes, TileFactoryInfo info, int zoom){ QuadBlockBuilder builder = new QuadBlockBuilder(); QuadBlock root = builder.build(writes, info, zoom); add(root, zoom); // try { // System.out.println(quadPositions.size() + " blocks written to tmp file in " + (tmpChannel.position()/1024) + " KB"); // } catch (Exception e) { // throw new RuntimeException(e); // } } void add(QuadBlock block, int zoom){ try { if(block.getNbLeaves()>0){ // allocate new block by saving its position int blockNb = quadPositions.size(); long pos = tmpChannel.position(); quadPositions.add(pos); List<PendingWrite> leaves = block.getLeaves(); // write all to a temporary byte array ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); // write block nb int headerSize =0; dos.writeInt(blockNb); headerSize+=4; // write bjson headerSize += ROGWriterUtils.writeByteArray(block.getBjson(), dos); // write number of leaves int n = leaves.size(); dos.writeInt(n); headerSize+=4; // write position of all leaves relative to block start headerSize += 4*n; int leafPos = headerSize; for(int i =0 ; i<n ; i++){ dos.writeInt(leafPos); PendingWrite pw = leaves.get(i); pw.index.setBlock(blockNb, i, zoom); leafPos+= ROGWriterUtils.getWrittenGeomSize(pw.bjsonBytes, pw.geomBytes); } // now write the leaves themselves for(int i =0 ; i<n ; i++){ PendingWrite pw = leaves.get(i); ROGWriterUtils.writeGeom(pw.index.rowNb,pw.bjsonBytes, pw.geomBytes, dos); } // now write the whole block to disk tmpChannel.write(ByteBuffer.wrap(baos.toByteArray())); } for(int i =0 ; i<block.getNbChildren();i++){ add(block.getChild(i),zoom); } } catch (Exception e) { throw new RuntimeException(e); } } private void writeBJSONFileHeader(boolean isNOLPL,DataOutputStream dos){ try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); BsonFactory factory = new BsonFactory(); JsonGenerator gen = factory.createJsonGenerator(baos); gen.writeStartObject(); gen.writeFieldName(RogReaderUtils.VERSION_KEY); gen.writeNumber(RogReaderUtils.RENDER_GEOMETRY_FILE_VERSION); gen.writeFieldName(RogReaderUtils.IS_NOPL_KEY); gen.writeBoolean(isNOLPL); gen.writeEndObject(); gen.close(); ROGWriterUtils.writeByteArray(baos.toByteArray(), dos); } catch (Exception e) { throw new RuntimeException(e); } } private void writeObjectIndex(boolean isNOLPL,List<ShapeIndex> indices,DataOutputStream out) throws IOException { writeBJSONFileHeader(isNOLPL, out); out.writeInt(indices.size()); // Write each entry to header for (int i = 0; i < indices.size(); i++) { // write row number / id ShapeIndex indx = indices.get(i); out.writeLong(indx.rowNb); // write total points count out.writeInt(indx.nbPointsFullGeometry); // write count by shape out.writeInt(indx.pointsCount); out.writeInt(indx.linestringsCount); out.writeInt(indx.polysCount); // write bounds out.writeDouble(indx.getWgsBounds().getMinX()); out.writeDouble(indx.getWgsBounds().getMinY()); out.writeDouble(indx.getWgsBounds().getWidth()); out.writeDouble(indx.getWgsBounds().getHeight()); // write latitude out.writeDouble(indx.getWgsCentroid().getLongitude()); out.writeDouble(indx.getWgsCentroid().getLatitude()); // write full geometry position out.writeInt(indx.originalWGS84BlockNb); out.writeInt(indx.originalWGS84GeomNbInBlock); // write position array size out.writeByte(indx.blockNb.length); // write position array for (int j = 0; j < indx.blockNb.length; j++) { out.writeInt(indx.blockNb[j]); } for (int j = 0; j < indx.blockNb.length; j++) { out.writeInt(indx.geomNbInBlock[j]); } // write binary json data if we have it ROGWriterUtils.writeByteArray(indx.binaryJSONData, out); } // ensure whole header is written to physical file out.flush(); } }