Goal

When the user makes changes to the data, those modifications often need to be saved, to disk or to a database for example.

The goal of this tutorial is to illustrate how you can save the contents of an ILcdModel. For this purpose, the tutorial uses the custom data format introduced in the Support a custom vector format tutorial.

The LuciadLightspeed API uses the concept of an ILcdModelEncoder to save the contents of an ILcdModel.

An ILcdModelEncoder offers two methods for encoding an ILcdModel:

  • save: this method encodes the ILcdModel to its native format.

    For example, if you use this method for SHP data with an encoder capable of writing SHP files, it will write the model contents to a SHP file.

    The models for which an encoder is available in the LuciadLightspeed API typically expose their native encoder through the ILcdModel.getModelEncoder() method.

    For example:

    TLcdCompositeModelDecoder decoder = new TLcdCompositeModelDecoder(
        TLcdServiceLoader.getInstance(ILcdModelDecoder.class)
    );
    ILcdModel model = decoder.decode(pathToGeoJsonFile);
    
    // Make some changes to the model
    // For example add, remove or update elements
    
    // Save those changes back to disk
    ILcdModelEncoder modelEncoder = model.getModelEncoder();
    if (modelEncoder != null && modelEncoder.canSave(model)) {
      //This will override the file located at 'pathToGeoJsonFile'
      modelEncoder.save(model);
    } else {
      // this kind of model can't be saved back
    }
  • export: this method encodes the ILcdModel to a specific destination. This is similar to the "Save As" function in many applications.

    The export method can also accept models with a different native format, similar to the "Export" function in many applications.

    For example, it can be used to write SHP data to a GeoJSON file:

    //Decode a .shp file
    TLcdCompositeModelDecoder decoder = new TLcdCompositeModelDecoder(
        TLcdServiceLoader.getInstance(ILcdModelDecoder.class)
    );
    ILcdModel model = decoder.decode(pathToSHPFile);
    
    TLcdGeoJsonModelEncoder geoJsonModelEncoder = new TLcdGeoJsonModelEncoder();
    
    if (geoJsonModelEncoder.canExport(model, geoJsonOutputPath)) {
      //This will create a .geojson file at 'geoJsonOutputPath'
      geoJsonModelEncoder.export(model, geoJsonOutputPath);
    } else {
      // can happen, but probably not in this case
    }

In this tutorial, vector data is used, but the same concepts apply to raster data, and you can use the same API.

Creating the model encoder

The model encoder we create in this tutorial has the following properties:

  • It supports waypoint models only.

  • It does not support saving waypoint models to another data format.

We start by creating a new class that implements the ILcdModelEncoder interface:

public class WayPointsModelEncoder implements ILcdModelEncoder {
}

Similar to an ILcdModelDecoder, a model encoder also has a display name. The display name is a human-readable name for the format that the model encoder can encode:

@Override
public String getDisplayName() {
  return "Way Points";
}

We start by writing the export functionality of the model encoder. Each model encoder has a canExport method that indicates whether the encoder can handle the ILcdModel.

In this case, we only support waypoint models that are stored to a waypoint file:

@Override
public boolean canExport(ILcdModel aModel, String aDestinationName) {
  return isWaypointModel(aModel) && isValidDestinationName(aDestinationName);
}

private boolean isWaypointModel(ILcdModel aModel) {
  return "CWP".equals(aModel.getModelDescriptor().getTypeName());
}

private boolean isValidDestinationName(String aTargetName) {
  return aTargetName != null && aTargetName.endsWith(".cwp");
}

The export method loops over each element, and writes them to the file:

@Override
public void export(ILcdModel aModel, String aDestinationName) throws IllegalArgumentException, IOException {
  if (!canExport(aModel, aDestinationName)) {
    throw new IllegalArgumentException(String.format("Cannot export %s to %s", aModel.getModelDescriptor().getDisplayName(), aDestinationName));
  }
  //Loop over all elements, and write them to the file one by one
  TLcdFileOutputStreamFactory outputStreamFactory = new TLcdFileOutputStreamFactory();
  try (OutputStream os = outputStreamFactory.createOutputStream(aDestinationName);
       PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os)))) {
    try (TLcdLockUtil.Lock autoUnlock = TLcdLockUtil.readLock(aModel)) {
      Enumeration elements = aModel.elements();
      while (elements.hasMoreElements()) {
        ILcdDataObject wayPoint = (ILcdDataObject) elements.nextElement();
        writeRecord(wayPoint, writer);
      }
    }
  }

  //Update the source name on the model to reflect the latest location
  try (TLcdLockUtil.Lock autoUnlock = TLcdLockUtil.writeLock(aModel)) {
    TLcdModelDescriptor waypointModelDescriptor = (TLcdModelDescriptor) aModel.getModelDescriptor();
    waypointModelDescriptor.setSourceName(aDestinationName);
  }

}

private void writeRecord(ILcdDataObject aWayPoint, PrintWriter aWriter) throws IOException {
  aWriter.println(aWayPoint.getValue("identifier"));

  TLcdLonLatHeightPoint point =
      (TLcdLonLatHeightPoint) aWayPoint.getValue("location");

  double lon = point.getX();
  double lat = point.getY();
  double height = point.getZ();

  aWriter.print(lon);
  aWriter.print(" ");

  aWriter.print(lat);
  aWriter.print(" ");

  aWriter.print(height);

  aWriter.println();
}

See the Support a custom vector format for a description of the waypoint format.

If you save a model, instead of exporting it, you can re-use this code. You can consider saving a model as exporting it to its original location, overwriting the original file:

@Override
public boolean canSave(ILcdModel aModel) {
  return canExport(aModel, aModel.getModelDescriptor().getSourceName());
}

@Override
public void save(ILcdModel aModel) throws IllegalArgumentException, IOException {
  export(aModel, aModel.getModelDescriptor().getSourceName());
}

Full code

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Enumeration;

import com.luciad.datamodel.ILcdDataObject;
import com.luciad.io.TLcdFileOutputStreamFactory;
import com.luciad.model.ILcdModel;
import com.luciad.model.ILcdModelEncoder;
import com.luciad.model.TLcdModelDescriptor;
import com.luciad.shape.shape3D.TLcdLonLatHeightPoint;
import com.luciad.util.concurrent.TLcdLockUtil;

public class WayPointsModelEncoder implements ILcdModelEncoder {

  @Override
  public String getDisplayName() {
    return "Way Points";
  }

  @Override
  public boolean canSave(ILcdModel aModel) {
    return canExport(aModel, aModel.getModelDescriptor().getSourceName());
  }

  @Override
  public void save(ILcdModel aModel) throws IllegalArgumentException, IOException {
    export(aModel, aModel.getModelDescriptor().getSourceName());
  }

  @Override
  public boolean canExport(ILcdModel aModel, String aDestinationName) {
    return isWaypointModel(aModel) && isValidDestinationName(aDestinationName);
  }

  private boolean isWaypointModel(ILcdModel aModel) {
    return "CWP".equals(aModel.getModelDescriptor().getTypeName());
  }

  private boolean isValidDestinationName(String aTargetName) {
    return aTargetName != null && aTargetName.endsWith(".cwp");
  }

  @Override
  public void export(ILcdModel aModel, String aDestinationName) throws IllegalArgumentException, IOException {
    if (!canExport(aModel, aDestinationName)) {
      throw new IllegalArgumentException(String.format("Cannot export %s to %s", aModel.getModelDescriptor().getDisplayName(), aDestinationName));
    }
    //Loop over all elements, and write them to the file one by one
    TLcdFileOutputStreamFactory outputStreamFactory = new TLcdFileOutputStreamFactory();
    try (OutputStream os = outputStreamFactory.createOutputStream(aDestinationName);
         PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os)))) {
      try (TLcdLockUtil.Lock autoUnlock = TLcdLockUtil.readLock(aModel)) {
        Enumeration elements = aModel.elements();
        while (elements.hasMoreElements()) {
          ILcdDataObject wayPoint = (ILcdDataObject) elements.nextElement();
          writeRecord(wayPoint, writer);
        }
      }
    }

    //Update the source name on the model to reflect the latest location
    try (TLcdLockUtil.Lock autoUnlock = TLcdLockUtil.writeLock(aModel)) {
      TLcdModelDescriptor waypointModelDescriptor = (TLcdModelDescriptor) aModel.getModelDescriptor();
      waypointModelDescriptor.setSourceName(aDestinationName);
    }

  }

  private void writeRecord(ILcdDataObject aWayPoint, PrintWriter aWriter) throws IOException {
    aWriter.println(aWayPoint.getValue("identifier"));

    TLcdLonLatHeightPoint point =
        (TLcdLonLatHeightPoint) aWayPoint.getValue("location");

    double lon = point.getX();
    double lat = point.getY();
    double height = point.getZ();

    aWriter.print(lon);
    aWriter.print(" ");

    aWriter.print(lat);
    aWriter.print(" ");

    aWriter.print(height);

    aWriter.println();
  }

}