private PlacemarkType createTrack(KmlRoute route, int startIndex, int endIndex) {
   ObjectFactory objectFactory = new ObjectFactory();
   PlacemarkType placemarkType = objectFactory.createPlacemarkType();
   placemarkType.setName(TRACK);
   placemarkType.setDescription(asDescription(route.getDescription()));
   placemarkType.setStyleUrl("#" + TRACK_LINE_STYLE);
   // create gx:Track if there are at least two positions with a time stamp
   if (containTime(route)) {
     slash.navigation.kml.binding22gx.ObjectFactory gxObjectFactory =
         new slash.navigation.kml.binding22gx.ObjectFactory();
     TrackType trackType = gxObjectFactory.createTrackType();
     List<KmlPosition> positions = route.getPositions();
     for (int i = startIndex; i < endIndex; i++) {
       KmlPosition position = positions.get(i);
       String time = position.hasTime() ? formatDate(position.getTime()) : "";
       trackType.getWhen().add(time);
     }
     for (int i = startIndex; i < endIndex; i++) {
       KmlPosition position = positions.get(i);
       trackType.getCoord().add(createCoordinates(position, true));
     }
     placemarkType.setAbstractGeometryGroup(gxObjectFactory.createTrack(trackType));
   } else {
     LineStringType lineStringType = objectFactory.createLineStringType();
     placemarkType.setAbstractGeometryGroup(objectFactory.createLineString(lineStringType));
     List<String> coordinates = lineStringType.getCoordinates();
     List<KmlPosition> positions = route.getPositions();
     for (int i = startIndex; i < endIndex; i++) {
       KmlPosition position = positions.get(i);
       coordinates.add(createCoordinates(position, false));
     }
   }
   return placemarkType;
 }
 private Vec2Type createVec2Type(double x, double y, UnitsEnumType unitX, UnitsEnumType unitY) {
   ObjectFactory objectFactory = new ObjectFactory();
   Vec2Type vec2Type = objectFactory.createVec2Type();
   vec2Type.setX(x);
   vec2Type.setY(y);
   vec2Type.setXunits(unitX);
   vec2Type.setYunits(unitY);
   return vec2Type;
 }
 private StyleType createLineStyle(String styleName, double width, byte[] color) {
   ObjectFactory objectFactory = new ObjectFactory();
   StyleType styleType = objectFactory.createStyleType();
   styleType.setId(styleName);
   LineStyleType lineStyleType = objectFactory.createLineStyleType();
   styleType.setLineStyle(lineStyleType);
   lineStyleType.setColor(color);
   lineStyleType.setWidth(width);
   return styleType;
 }
 private PlacemarkType createMark(int kiloMeter, double longitude, double latitude) {
   ObjectFactory objectFactory = new ObjectFactory();
   PlacemarkType placeMark = objectFactory.createPlacemarkType();
   placeMark.setName(kiloMeter + ". Km");
   placeMark.setVisibility(FALSE);
   PointType point = objectFactory.createPointType();
   point
       .getCoordinates()
       .add(formatPositionAsString(longitude) + "," + formatPositionAsString(latitude) + ",0");
   placeMark.setAbstractGeometryGroup(objectFactory.createPoint(point));
   return placeMark;
 }
 private PlacemarkType createSpeedSegment(
     int currentSegment, int speedClass, List<String> coordinates) {
   ObjectFactory objectFactory = new ObjectFactory();
   PlacemarkType placemarkType = objectFactory.createPlacemarkType();
   placemarkType.setName("Segment " + currentSegment);
   placemarkType.setDescription(getSpeedDescription(speedClass));
   placemarkType.setStyleUrl('#' + getSpeedColor(speedClass));
   placemarkType.setVisibility(FALSE);
   LineStringType lineStringType = objectFactory.createLineStringType();
   lineStringType.getCoordinates().addAll(coordinates);
   placemarkType.setAbstractGeometryGroup(objectFactory.createLineString(lineStringType));
   return placemarkType;
 }
  private ScreenOverlayType createScreenOverlayImage(
      String name, String url, Vec2Type overlayXY, Vec2Type screenXY, Vec2Type size) {
    ObjectFactory objectFactory = new ObjectFactory();
    ScreenOverlayType screenOverlayType = objectFactory.createScreenOverlayType();
    screenOverlayType.setName(name);
    screenOverlayType.setOverlayXY(overlayXY);
    screenOverlayType.setScreenXY(screenXY);
    screenOverlayType.setSize(size);

    LinkType icon = objectFactory.createLinkType();
    icon.setHref(url);
    screenOverlayType.setIcon(icon);
    return screenOverlayType;
  }
  private FolderType createSpeed(KmlRoute route, int startIndex, int endIndex) {
    ObjectFactory objectFactory = new ObjectFactory();
    FolderType folderType = objectFactory.createFolderType();
    folderType.setName(SPEED);
    folderType.setVisibility(FALSE);
    folderType.setOpen(FALSE);
    folderType.getAbstractFeatureGroup().add(objectFactory.createScreenOverlay(createSpeedbar()));

    int segmentIndex = 0;
    List<String> coordinates = new ArrayList<>();
    Integer previousSpeedClass = null;
    Double previousSpeed = null;
    KmlPosition previous = null;
    List<KmlPosition> positions = route.getPositions();

    // since the speed of a position is the average speed of the previous segment
    for (int i = startIndex; i < endIndex; i++) {
      KmlPosition position = positions.get(i);

      Double speed = null;
      if (position.hasSpeed()) speed = position.getSpeed();
      else if (previous != null) speed = previous.calculateSpeed(position);
      if (speed == null) speed = previousSpeed;
      if (speed == null) continue;

      coordinates.add(createCoordinates(position, false));

      int speedClass = getSpeedClass(speed);
      if (previousSpeedClass != null && previousSpeedClass != speedClass) {
        PlacemarkType placemarkType =
            createSpeedSegment(++segmentIndex, previousSpeedClass, coordinates);
        folderType.getAbstractFeatureGroup().add(objectFactory.createPlacemark(placemarkType));

        coordinates.clear();
        coordinates.add(createCoordinates(position, false));
      }

      previousSpeedClass = speedClass;
      previousSpeed = speed;
      previous = position;
    }

    return segmentIndex > 0 ? folderType : null;
  }
 private FolderType createWayPoints(KmlRoute route, int startIndex, int endIndex) {
   ObjectFactory objectFactory = new ObjectFactory();
   FolderType folderType = objectFactory.createFolderType();
   folderType.setName(WAYPOINTS);
   folderType.setDescription(asDescription(route.getDescription()));
   List<KmlPosition> positions = route.getPositions();
   for (int i = startIndex; i < endIndex; i++) {
     KmlPosition position = positions.get(i);
     PlacemarkType placemarkType = objectFactory.createPlacemarkType();
     folderType.getAbstractFeatureGroup().add(objectFactory.createPlacemark(placemarkType));
     placemarkType.setName(asName(isWriteName() ? position.getDescription() : null));
     placemarkType.setDescription(asDesc(isWriteDesc() ? position.getDescription() : null));
     if (position.hasTime()) {
       TimeStampType timeStampType = objectFactory.createTimeStampType();
       timeStampType.setWhen(formatDate(position.getTime()));
       placemarkType.setAbstractTimePrimitiveGroup(objectFactory.createTimeStamp(timeStampType));
     }
     PointType pointType = objectFactory.createPointType();
     placemarkType.setAbstractGeometryGroup(objectFactory.createPoint(pointType));
     pointType.getCoordinates().add(createCoordinates(position, false));
   }
   return folderType;
 }
 private PlacemarkType createRoute(KmlRoute route) {
   ObjectFactory objectFactory = new ObjectFactory();
   PlacemarkType placemarkType = objectFactory.createPlacemarkType();
   placemarkType.setName(ROUTE);
   placemarkType.setDescription(asDescription(route.getDescription()));
   placemarkType.setStyleUrl("#" + ROUTE_LINE_STYLE);
   MultiGeometryType multiGeometryType = objectFactory.createMultiGeometryType();
   placemarkType.setAbstractGeometryGroup(objectFactory.createMultiGeometry(multiGeometryType));
   LineStringType lineStringType = objectFactory.createLineStringType();
   multiGeometryType
       .getAbstractGeometryGroup()
       .add(objectFactory.createLineString(lineStringType));
   List<String> coordinates = lineStringType.getCoordinates();
   for (KmlPosition position : route.getPositions()) {
     coordinates.add(createCoordinates(position, false));
   }
   return placemarkType;
 }
  private KmlType createKmlType(List<KmlRoute> routes) {
    ObjectFactory objectFactory = new ObjectFactory();
    KmlType kmlType = objectFactory.createKmlType();
    DocumentType documentType = objectFactory.createDocumentType();
    kmlType.setAbstractFeatureGroup(objectFactory.createDocument(documentType));
    /* might make sense for Waypoint lists with one position lists in the file
    if(routes.size() == 1) {
        KmlRoute route = routes.get(0);
        documentType.setName(createDocumentName(route));
        documentType.setDescription(asDescription(route.getDescription()));
    }
    */
    documentType.setOpen(TRUE);

    if (hasCharacteristics(routes, Route))
      documentType
          .getAbstractStyleSelectorGroup()
          .add(
              objectFactory.createStyle(
                  createLineStyle(ROUTE_LINE_STYLE, getLineWidth(), getRouteLineColor())));
    if (hasCharacteristics(routes, Track)) {
      documentType
          .getAbstractStyleSelectorGroup()
          .add(
              objectFactory.createStyle(
                  createLineStyle(TRACK_LINE_STYLE, getLineWidth(), getTrackLineColor())));
      if (isWriteSpeed())
        for (StyleType style : createSpeedTrackColors(getSpeedLineWidth()))
          documentType.getAbstractStyleSelectorGroup().add(objectFactory.createStyle(style));
    }

    for (KmlRoute route : routes) {
      switch (route.getCharacteristics()) {
        case Waypoints:
          FolderType wayPoints = createWayPoints(route, 0, route.getPositionCount());
          documentType.getAbstractFeatureGroup().add(objectFactory.createFolder(wayPoints));
          break;
        case Route:
          FolderType routeFolder = objectFactory.createFolderType();
          routeFolder.setName(createPlacemarkName(ROUTE, route));
          documentType.getAbstractFeatureGroup().add(objectFactory.createFolder(routeFolder));

          PlacemarkType routePlacemarks = createRoute(route);
          routeFolder.getAbstractFeatureGroup().add(objectFactory.createPlacemark(routePlacemarks));
          if (isWriteMarks())
            routeFolder
                .getAbstractFeatureGroup()
                .add(objectFactory.createFolder(createMarks(route, 0, route.getPositionCount())));
          break;
        case Track:
          FolderType trackFolder = objectFactory.createFolderType();
          trackFolder.setName(createPlacemarkName(TRACK, route));
          documentType.getAbstractFeatureGroup().add(objectFactory.createFolder(trackFolder));

          PlacemarkType track = createTrack(route, 0, route.getPositionCount());
          trackFolder.getAbstractFeatureGroup().add(objectFactory.createPlacemark(track));
          if (isWriteSpeed()) {
            FolderType speed = createSpeed(route, 0, route.getPositionCount());
            if (speed != null)
              trackFolder.getAbstractFeatureGroup().add(objectFactory.createFolder(speed));
          }
          if (isWriteMarks())
            trackFolder
                .getAbstractFeatureGroup()
                .add(objectFactory.createFolder(createMarks(route, 0, route.getPositionCount())));
          break;
        default:
          throw new IllegalArgumentException(
              "Unknown RouteCharacteristics " + route.getCharacteristics());
      }
    }
    return kmlType;
  }
  protected KmlType createKmlType(KmlRoute route, int startIndex, int endIndex) {
    ObjectFactory objectFactory = new ObjectFactory();
    KmlType kmlType = objectFactory.createKmlType();
    DocumentType documentType = objectFactory.createDocumentType();
    kmlType.setAbstractFeatureGroup(objectFactory.createDocument(documentType));
    documentType.setName(createDocumentName(route));
    documentType.setDescription(asDescription(route.getDescription()));
    documentType.setOpen(TRUE);

    if (hasCharacteristics(singletonList(route), Route))
      documentType
          .getAbstractStyleSelectorGroup()
          .add(
              objectFactory.createStyle(
                  createLineStyle(ROUTE_LINE_STYLE, getLineWidth(), getRouteLineColor())));
    if (hasCharacteristics(singletonList(route), Track)) {
      documentType
          .getAbstractStyleSelectorGroup()
          .add(
              objectFactory.createStyle(
                  createLineStyle(TRACK_LINE_STYLE, getLineWidth(), getTrackLineColor())));
      if (isWriteSpeed())
        for (StyleType style : createSpeedTrackColors(getSpeedLineWidth()))
          documentType.getAbstractStyleSelectorGroup().add(objectFactory.createStyle(style));
    }

    FolderType folderType = createWayPoints(route, startIndex, endIndex);
    documentType.getAbstractFeatureGroup().add(objectFactory.createFolder(folderType));

    PlacemarkType placemarkTrack = createTrack(route, startIndex, endIndex);
    documentType.getAbstractFeatureGroup().add(objectFactory.createPlacemark(placemarkTrack));

    if (hasCharacteristics(singletonList(route), Track)) {
      FolderType speed = createSpeed(route, startIndex, endIndex);
      if (speed != null)
        documentType.getAbstractFeatureGroup().add(objectFactory.createFolder(speed));
    }
    if (!route.getCharacteristics().equals(Waypoints) && isWriteMarks()) {
      FolderType marks = createMarks(route, startIndex, endIndex);
      documentType.getAbstractFeatureGroup().add(objectFactory.createFolder(marks));
    }
    return kmlType;
  }
  private FolderType createMarks(KmlRoute route, int startIndex, int endIndex) {
    ObjectFactory objectFactory = new ObjectFactory();

    FolderType marks = objectFactory.createFolderType();
    marks.setName(MARKS);
    marks.setVisibility(FALSE);
    marks.setOpen(FALSE);

    double currentDistance = 0, previousDistance = 0;
    int currentKiloMeter = 1;
    List<KmlPosition> positions = route.getPositions();
    for (int i = startIndex + 1; i < endIndex; i++) {
      KmlPosition previousPosition = positions.get(i - 1);
      KmlPosition currentPosition = positions.get(i);

      Double distance = currentPosition.calculateDistance(previousPosition);
      if (isEmpty(distance)) continue;

      currentDistance += distance;
      if (currentDistance >= METERS_BETWEEN_MARKS) {
        // calculate the point at the kilometer mark that's between the current position and the
        // previous one.
        // it is possible, that there's more than one point to create
        // see: http://www.movable-type.co.uk/scripts/latlong.html and
        // http://williams.best.vwh.net/avform.htm#LL
        KmlPosition intermediate =
            new KmlPosition(
                previousPosition.getLongitude(),
                previousPosition.getLatitude(),
                null,
                null,
                null,
                null);

        // remaining distance between the last point and the mark
        double remainingDistance = METERS_BETWEEN_MARKS - (previousDistance % METERS_BETWEEN_MARKS);
        do {
          double angle = toRadians(intermediate.calculateAngle(currentPosition));
          double latitude1 = toRadians(intermediate.getLatitude());
          double longitude1 = toRadians(intermediate.getLongitude());
          double latitude2 =
              asin(
                  sin(latitude1) * cos(remainingDistance / EARTH_RADIUS)
                      + cos(latitude1) * sin(remainingDistance / EARTH_RADIUS) * cos(angle));
          double longitude2 =
              longitude1
                  + atan2(
                      sin(angle) * sin(remainingDistance / EARTH_RADIUS) * cos(latitude1),
                      cos(remainingDistance / EARTH_RADIUS) - sin(latitude1) * sin(latitude2));
          intermediate.setLatitude(toDegrees(latitude2));
          intermediate.setLongitude(toDegrees(longitude2));

          PlacemarkType placeMark =
              createMark(
                  currentKiloMeter++, intermediate.getLongitude(), intermediate.getLatitude());
          marks.getAbstractFeatureGroup().add(objectFactory.createPlacemark(placeMark));

          remainingDistance = METERS_BETWEEN_MARKS;
        } while (toDouble(intermediate.calculateDistance(currentPosition)) > METERS_BETWEEN_MARKS);

        currentDistance = currentDistance % METERS_BETWEEN_MARKS;
      }
      previousDistance = currentDistance;
    }
    return marks;
  }