1

I have already made KML/KMZ files using org.geotools in Java (see below). But now we have got so large files that I need to cut them into regions and use networkLinks. This is mentioned in https://developers.google.com/kml/documentation/kmlreference#networklink .

How do I do that in Java using org.geotools? (Nothing goes wrong with the code below, but it does not include regions and networkLinks.)

public void export(Collection<Occurrence> data, OutputStream os)
{  
  SimpleFeatureCollection features = createSampleFeatures(data, true);
  Encoder encoder = new Encoder(new KMLConfiguration());
  encoder.setIndenting(true);

  try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ZipOutputStream zos = new ZipOutputStream(os);
    ByteArrayOutputStream tempOs = new ByteArrayOutputStream())
    {
       encoder.encode(features, KML.kml, tempOs);

       deleteRootElement(baos, tempOs);

       ZipEntry ze = new ZipEntry("doc.kml");

       zos.putNextEntry(ze);

       PrintStream ps = new PrintStream(zos);
       ps.println(baos.toString());
       ps.flush();
       zos.closeEntry();
       }
       catch (IOException | TransformerFactoryConfigurationError | TransformerException e)
       {

       }
    }
}

with

protected SimpleFeatureCollection createSampleFeatures(
   Collection<Occurrence> occurrences, boolean withLink)
{
  SimpleFeatureTypeBuilder typeBuilder = new SimpleFeatureTypeBuilder();
  typeBuilder.setName("occurrences");
  typeBuilder.add("geometry", Geometry.class, DefaultGeographicCRS.WGS84);
  typeBuilder.add("name", String.class);
  typeBuilder.add("description", Object.class);
  SimpleFeatureType type = typeBuilder.buildFeatureType();

  SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(type);
  List<SimpleFeature> simpleFeatures = new ArrayList<>();
  CoordinateTransformerFactory factory = new CoordinateTransformerFactory();

  Iterator<Occurrence> iterator = occurrences.iterator();
  while (iterator.hasNext())
  {
    Occurrence occurrence = iterator.next();
    String wkt = 4326;

    WKTReader wktReader = new WKTReader();
    try
    {
      Geometry geom = wktReader.read(wkt);
      featureBuilder.add(geom);
    }
    catch (ParseException e)
    {
    }
    featureBuilder.add(occurrence.getTaxon().getName());
    String link = "this is a link";

    featureBuilder
       .add(link + " "
         + StringFormatter.getNullSafeStringRepresentation(
           occurrence.getSample().getSurvey().getAvailability())
         + " " + occurrence.getSample().getDate() + " Fund-UUID: "
         + occurrence.getUuid());
       simpleFeatures.add(featureBuilder
       .buildFeature("uuid." + occurrence.getUuid().toString()));
     }

     return DataUtilities.collection(simpleFeatures);
}

and

// Workaround for https://trac.osgeo.org/gdal/ticket/6981
// remove after fix is published for recent gdal and qgis versions

protected void deleteRootElement(OutputStream os, ByteArrayOutputStream tempOs)
 throws TransformerFactoryConfigurationError, TransformerException
 {
   TransformerFactory factory = TransformerFactory.newInstance();
   Source xslt = new StreamSource(this.getClass().getResourceAsStream("/removeNs.xslt"));
  Transformer transformer = factory.newTransformer(xslt);
  Source text = new StreamSource(new ByteArrayInputStream(tempOs.toByteArray()));
  transformer.transform(text, new StreamResult(os));
}
0

Since I could not find an answer to do this with geotools.org I wrote some code myself. First I devided up the data (occurrences) into chunks. Then I encoded each chunk into a kml file and placed it as ZipEntry into the kmz file into the subfolder "regions". Then I made a parent doc.kml file that is placed as ZipEntry into the kmz file. Here is the code:

  Set<List<Occurrence>> occurrenceLists = divideUpData(occurrences);

  try (ZipOutputStream zos = new ZipOutputStream(outputStream);)
  {
     Map<String, LatLonAltBox> networkLinks = new HashMap<>();

     ZipEntry ze0 = new ZipEntry("regions/");
     zos.putNextEntry(ze0);
     zos.closeEntry();

     Iterator iterator = occurrenceLists.iterator();
     int index = 0;
     while (iterator.hasNext())
     {
        List<Occurrence> occurrenceList = (List<Occurrence>) iterator
              .next();
        String link = "regions/region" + index + ".kml";

        SimpleFeatureCollection features = createSampleFeatures(
              occurrenceList);
        Encoder encoder = new Encoder(new KMLConfiguration());

        try (ByteArrayOutputStream tempOs = new ByteArrayOutputStream();
              ByteArrayOutputStream baos = new ByteArrayOutputStream())
        {
           encoder.encode(features, KML.kml, tempOs);
           deleteRootElement(baos, tempOs);
           ZipEntry ze = new ZipEntry(link);
           zos.putNextEntry(ze);
           PrintStream ps = new PrintStream(zos);
           ps.println(baos.toString());
           ps.flush();
           zos.closeEntry();
           networkLinks.put(link, readNetworkLink(occurrenceList));
           index++;
        } catch (IOException | TransformerFactoryConfigurationError | TransformerException e)
        {
           LOGGER.error(
                 "Failure to encode occurrences as KMZ." + e.getMessage());
        }
     }

     ZipEntry ze = new ZipEntry("doc.kml");
     zos.putNextEntry(ze);
     PrintStream ps = new PrintStream(zos);
     ps.println(createParentFile(networkLinks));
     ps.flush();
     zos.closeEntry();

     zos.flush();
  } catch (IOException | TransformerFactoryConfigurationError e)
  {
     LOGGER.error("Failure to encode occurrences as KMZ." + e.getMessage());
  }

and

 protected String createParentFile(Map<String, LatLonAltBox> networkLinks)
 {
  String west = String.valueOf(westmostPosition);
  String south = String.valueOf(southmostPosition);
  String north = String.valueOf(northmostPosition);
  String east = String.valueOf(eastmostPosition);

  StringBuilder builder = new StringBuilder();
  builder.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
  builder.append("<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n");
  builder.append("<Document>\n");
  builder.append("<name>Gesamtgebiet</name>\n");

  builder.append("<Region>\n");
  builder.append("<LatLonAltBox>\n");
  builder.append("<north>" + north + "</north>\n");
  builder.append("<south>" + south + "</south>\n");
  builder.append("<east>" + east + "</east>\n");
  builder.append("<west>" + west + "</west>\n");
  builder.append("</LatLonAltBox>\n");
  builder.append("</Region>\n");

  builder.append("<Placemark>\n");
  builder.append("<name>Fundgebiet</name>\n");
  builder.append("<LineString>\n");
  builder.append("<tessellate>1</tessellate>\n");
  builder.append("<coordinates>\n");
  builder.append(west+","+south+",0\n");
  builder.append(east+","+south+",0\n");
  builder.append(east+","+north+",0\n");
  builder.append(west+","+north+",0\n");
  builder.append(west+","+south+",0\n");
  builder.append("</coordinates>");
  builder.append("</LineString>\n");
  builder.append("</Placemark>\n");

  for(String file: networkLinks.keySet())
  {
     createNetworkLink(builder, file, networkLinks.get(file));
  }

  builder.append("</Document>\n");
  builder.append("</kml>\n");
  return builder.toString();
}

private void createNetworkLink(StringBuilder builder, String childFile, LatLonAltBox box)
{
  builder.append("<NetworkLink>\n");
  builder.append("<name>Ebene_"+box.getMtb()+"</name>\n");

  builder.append("<Region>\n");
  builder.append("<LatLonAltBox>\n");
  builder.append("<north>"+box.getNorth()+"</north>\n");
  builder.append("<south>"+box.getSouth()+"</south>\n");
  builder.append("<east>"+box.getEast()+"</east>\n");
  builder.append("<west>"+box.getWest()+"</west>\n");
  builder.append("</LatLonAltBox>\n");
  builder.append("</Region>\n");

  builder.append("<Link>\n");
  builder.append("<href>"+childFile+"</href>\n");
  builder.append("<viewRefreshMode>onRegion</viewRefreshMode>\n");
  builder.append("</Link>\n");

  builder.append("</NetworkLink>\n");
}

with

 public class LatLonAltBox
 {
 private String west;
 private String east;
 private String north;
 private String south;
 private String mtb;

 // with getters and setters
}

and

 protected LatLonAltBox readNetworkLink(List<Occurrence> occurrenceList)
 {
 // some code here
 LatLonAltBox box = new LatLonAltBox();
 // some code here
 return box;
 }

The resulting xxxx.kmz file can be directly read by Google Earth. For QGIS change the ending .kmz into .zip and extract the regionX.kml files in the subfolder "regions". Add each .kml file to QGIS as a VectorLayer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.