package eu.simstadt.nf4j.async; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.osgeo.proj4j.BasicCoordinateTransform; import org.osgeo.proj4j.CRSFactory; import org.osgeo.proj4j.CoordinateReferenceSystem; import org.osgeo.proj4j.ProjCoordinate; import org.w3c.dom.Document; import org.w3c.dom.Element; import eu.simstadt.nf4j.InvalidJobDescriptorException; /** * Builds nF import and export jobs using nF's XML job format. Please read the nF manual if you want more details about * the numerous job attributes listed below. * * @author Marcel Bruse */ public class JobFileBuilderImpl implements JobFileBuilder { /** Supported version of the novaFACTORY. */ public static final String NOVA_FACTORY_VERSION = "6.3.1.1"; /** The version of the XML export job format. */ public static final String EXPORT_JOB_VERSION = "1.0.0"; /** * @return Returns the supported novaFACTORY version. */ @Override public String supportsNFVersion() { return NOVA_FACTORY_VERSION; } /** * @return Returns the supported XML export job version. */ @Override public String supportsExportJobVersion() { return EXPORT_JOB_VERSION; } /** * @return Returns the supported XML import job version. */ @Override public String supportsImportJobVersion() { return null; } /** * This is an intermediate prototype. * * @param jobDescriptor A job descriptor which describes the export job with all its attributes according to a valid * nF export job DTD. * @return Returns a string representation of the nF export job. * @throws FailedJobTransmissionException */ @Override public File buildExportJobFile(ExportJobDescription jobDescriptor) throws InvalidJobDescriptorException { File result = null; if (Objects.nonNull(jobDescriptor) && jobDescriptor.isValid()) { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.newDocument(); Element root = doc.createElement("EXPORT_JOB"); root.setAttribute("version", supportsExportJobVersion()); doc.appendChild(root); Element job = doc.createElement("job"); root.appendChild(job); Element initiator = doc.createElement("initiator"); initiator.appendChild(doc.createTextNode(jobDescriptor.getInitiator())); job.appendChild(initiator); Element jobnumber = doc.createElement("jobnumber"); jobnumber.appendChild(doc.createTextNode(jobDescriptor.getJobnumber())); job.appendChild(jobnumber); Element account = doc.createElement("account"); account.appendChild(doc.createTextNode(System.getProperty("user.name"))); job.appendChild(account); Element product = doc.createElement("product"); product.appendChild(doc.createTextNode(jobDescriptor.getProduct())); root.appendChild(product); Element layers = doc.createElement("layers"); layers.setAttribute("color", jobDescriptor.getColor()); layers.setAttribute("mono", jobDescriptor.getMono()); layers.setAttribute("plotLabelSrs", jobDescriptor.getPlotLabelSrs()); layers.setAttribute("plotframe", jobDescriptor.getPlotframe()); layers.setAttribute("single", jobDescriptor.getSingle()); root.appendChild(layers); appendLayers(doc, layers, jobDescriptor.getLayerList()); Element srs = doc.createElement("srs"); srs.appendChild(doc.createTextNode(jobDescriptor.getSrs())); root.appendChild(srs); Element extent = doc.createElement("extent"); extent.setAttribute("merge_mapsheets", jobDescriptor.getMergeMapsheets()); root.appendChild(extent); if (!jobDescriptor.getUnitList().isEmpty()) { extent.setAttribute("tile1asgn", jobDescriptor.getTile1asgn()); for (Unit unit : jobDescriptor.getUnitList()) { Element unitElement = doc.createElement("unit"); unitElement.setAttribute("exterior", unit.getExterior()); unitElement.setAttribute("frame", unit.getFrame()); unitElement.setAttribute("select_mapsheets", unit.getSelectMapsheets()); unitElement.setAttribute("subdivision", unit.getSubdivision()); unitElement.appendChild(doc.createTextNode(unit.getValue())); extent.appendChild(unitElement); } } else { Element polygon = createRegionPolygonElement(doc, jobDescriptor.regionPolygon); extent.appendChild(polygon); } Element resolution = doc.createElement("resolution"); resolution.appendChild(doc.createTextNode(jobDescriptor.getResolution())); root.appendChild(resolution); Element scale = doc.createElement("scale"); scale.appendChild(doc.createTextNode(jobDescriptor.getScale())); root.appendChild(scale); Element format = doc.createElement("format"); format.setAttribute("alphalinscale", "0.0"); format.setAttribute("alphascale", "1.0"); format.setAttribute("citygml_actfunc", "undef"); format.setAttribute("citygml_apptheme", ""); format.setAttribute("citygml_elemclasses", "true"); format.setAttribute("citygml_lodmode", "all"); format.setAttribute("citygml_lods", "01234"); format.setAttribute("citygml_metadata", "true"); format.setAttribute("citygml_outmode", "normal"); format.setAttribute("dtm", "false"); format.setAttribute("foredit", "false"); format.setAttribute("materialcopymode", "none"); format.setAttribute("polyopts_reverse", "false"); format.setAttribute("relcoords", "false"); format.setAttribute("rooftxr", "false"); format.setAttribute("roundcoords", "3"); format.setAttribute("schemetxr", "false"); format.setAttribute("solar", "false"); format.setAttribute("solargeoplex", "false"); format.setAttribute("tex", "false"); format.setAttribute("tolod1", "false"); format.setAttribute("xyz", "false"); format.appendChild(doc.createTextNode("CityGML")); root.appendChild(format); Element exportmetadata = doc.createElement("exportmetadata"); exportmetadata.setAttribute("calibration", jobDescriptor.getCalibration()); exportmetadata.setAttribute("xmetadata", jobDescriptor.getXmetadata()); exportmetadata.appendChild(doc.createTextNode(jobDescriptor.getExportmetadata())); root.appendChild(exportmetadata); Element addfile = doc.createElement("addfile"); addfile.setAttribute("col", jobDescriptor.getCol()); addfile.setAttribute("eck", jobDescriptor.getEck()); root.appendChild(addfile); Element usenodatamask = doc.createElement("usenodatamask"); usenodatamask.appendChild(doc.createTextNode(jobDescriptor.getUsenodatamask())); root.appendChild(usenodatamask); Element usepdctborderpoly = doc.createElement("usepdctborderpoly"); usepdctborderpoly.appendChild(doc.createTextNode(jobDescriptor.getUsepdctborderpoly())); root.appendChild(usepdctborderpoly); Element dhkresolvereferences = doc.createElement("dhkresolvereferences"); dhkresolvereferences.appendChild(doc.createTextNode(jobDescriptor.getDhkresolvereferences())); root.appendChild(dhkresolvereferences); Element zipresult = doc.createElement("zipresult"); zipresult.appendChild(doc.createTextNode(jobDescriptor.getZipresult())); root.appendChild(zipresult); Element userdescription = doc.createElement("userdescription"); root.appendChild(userdescription); Element namingpattern = doc.createElement("namingpattern"); root.appendChild(namingpattern); TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); StringWriter writer = new StringWriter(); StreamResult streamResult = new StreamResult(writer); transformer.transform(new DOMSource(doc), streamResult); File tempfile = File.createTempFile(jobDescriptor.getProduct() + "_", ".xml"); PrintWriter printWriter = new PrintWriter(tempfile); printWriter.print(writer.toString()); printWriter.close(); return tempfile; } catch (ParserConfigurationException ex) { // TODO Auto-generated catch block ex.printStackTrace(); } catch (TransformerConfigurationException ex) { // TODO Auto-generated catch block ex.printStackTrace(); } catch (TransformerException ex) { // TODO Auto-generated catch block ex.printStackTrace(); } catch (FileNotFoundException ex) { // TODO Auto-generated catch block ex.printStackTrace(); } catch (IOException ex) { // TODO Auto-generated catch block ex.printStackTrace(); } } else { throw new InvalidJobDescriptorException(); } return result; } /** * Appends layers to the XML job document. * * @param doc The XML document. * @param layers The XML layers element where new layers should be appended. * @param layerList The user defined list of layers. */ private void appendLayers(Document doc, Element layers, ArrayList layerList) { for (Layer layer : layerList) { Element layerElement = doc.createElement("layer"); layerElement.setAttribute("name", layer.getName()); String product = layer.getProduct(); if (Objects.nonNull(product) && !product.isEmpty()) { layerElement.setAttribute("product", product); } String style = layer.getStyle(); if (Objects.nonNull(style) && !style.isEmpty()) { layerElement.setAttribute("style", style); } layers.appendChild(layerElement); } } /** * Transforms a global WGS 84 position into a coordinate of the given target SRS. * * @param wgs84Position The WGS 84 position to be transformed to a position within the target SRS. * @param targetCRS The target SRS for the transformation. * @return The transformed target position within the target SRS. */ public static ProjCoordinate transformCoordinate(ProjCoordinate wgs84Position, CoordinateReferenceSystem targetCRS) { ProjCoordinate result = new ProjCoordinate(); CRSFactory f = new CRSFactory(); CoordinateReferenceSystem sourceCRS = f.createFromName(CRSWKT.EPSG_4326.wkt); // WGS 84 (used by Google Maps / OpenStreetMap) BasicCoordinateTransform transform = new BasicCoordinateTransform(sourceCRS, targetCRS); transform.transform(wgs84Position, result); return result; } /** * Appends the region polygon to the XML export job document. In order to do this, the given WGS 84 region polygon * will be transformed into a DHDN Gauss-Kruger zone 3 polygon. * * @param doc The XML export job document. * @param regionPolygon The polygon of the region which has been selected to be exported. * @return The w3c.dom.Element of the XML export job which describes the region polygon. */ private static Element createRegionPolygonElement(Document doc, List regionPolygon) { Element polygon = doc.createElement("polygon"); polygon.setAttribute("srs", "31467"); CRSFactory f = new CRSFactory(); CoordinateReferenceSystem targetCRS = f.createFromName(CRSWKT.EPSG_31467.wkt); // DHDN Gauss-Kruger zone 3 for (Coord coord : regionPolygon) { ProjCoordinate sourcePosition = new ProjCoordinate(coord.longitude, coord.latitude); ProjCoordinate targetPosition = transformCoordinate(sourcePosition, targetCRS); Element vertex = doc.createElement("vertex"); vertex.setAttribute("x", String.valueOf(targetPosition.x)); vertex.setAttribute("y", String.valueOf(targetPosition.y)); polygon.appendChild(vertex); } return polygon; } /** * Builds a zipped import job file. This file can be sent to a nF server instance by the caller afterwards. * * @param jobDescriptor A job descriptor which describes the import job with all its attributes according to a valid * nF import job DTD. * @return Returns a XML import job document. */ @Override public File buildImportJobFile(ImportJobDescription jobDescriptor) throws InvalidJobDescriptorException { if (Objects.nonNull(jobDescriptor) && jobDescriptor.isValid()) { try { // Write the nF start file which triggers and controls the processing of the CityGML file. String startFilename = jobDescriptor.getProduct() + "_" + jobDescriptor.getLeaf() + ".start"; File startfile = new File(System.getProperty("java.io.tmpdir"), startFilename); PrintWriter writer = new PrintWriter(startfile); writer.print(jobDescriptor.getLevel()); writer.close(); // Zip start file, CityGML file and ADE schemata File zippedCityGMLFile = File.createTempFile("nF_Import_", ".zip"); ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zippedCityGMLFile)); String zipFileName = jobDescriptor.getProduct() + "_" + jobDescriptor.getLeaf() + "_" + jobDescriptor.getLevel(); if (Objects.nonNull(jobDescriptor.getOperation())) { zipFileName += "_" + jobDescriptor.getOperation(); } zipFileName += ".gml"; File cityGMLFile = jobDescriptor.getCityGMLFile(); writeBytesToZipFile(new FileInputStream(cityGMLFile), zos, zipFileName); writeBytesToZipFile(new FileInputStream(startfile), zos, startFilename); for (File adeSchemaFile : jobDescriptor.getADESchemaFileList()) { writeBytesToZipFile(new FileInputStream(adeSchemaFile), zos, adeSchemaFile.getName()); } zos.close(); return zippedCityGMLFile; } catch (FileNotFoundException ex) { // TODO Auto-generated catch block ex.printStackTrace(); } catch (IOException ex) { // TODO Auto-generated catch block ex.printStackTrace(); } } else { throw new InvalidJobDescriptorException(); } return null; } /** * Writes a file to the given ZipOutputStream which compresses the file. * * @param fis The file input stream to be compressed. * @param zos The zip output stream. * @param zipEntry The new zip entry for the file to be compressed. * @throws IOException You will get some of this, if your streams point to nirvana. */ private void writeBytesToZipFile(FileInputStream fis, ZipOutputStream zos, String zipEntry) throws IOException { zos.putNextEntry(new ZipEntry(zipEntry)); byte[] b = new byte[1024]; int chunkSize; while ((chunkSize = fis.read(b)) > 0) { zos.write(b, 0, chunkSize); } fis.close(); } }