The LOS calculations queries the height of the point for which the LOS is calculated by looking at the Z coordinate of the point.

However, this only results in a number and no information about how to interpret that number. For example a value of 50 might mean an altitude of 50 meters above the terrain, but might just as well mean 50 meters above the ellipsoid.

This TLcdCoverageAltitudeMode information is in general not available, so Lucy does a best-effort to correctly interpret the height values.

This mode is defined in the TLspLOSProperties (see the setCenterPointAltitudeMode method). You can change the TLspLOSProperties for a line of sight using the setLOSProperties method of the TLcyLOSDomainObject.

Typically, when you know the altitude mode, you would adjust it when the LOS is created:

  • When you create the LOS using the API through the addLOSCoverage method on TLcyLOSManager, you can immediately adjust the altitude mode on the returned TLcyLOSDomainObject:

    TLcyLOSManager losManager = aLucyEnv.getService(TLcyLOSManager.class);
    if (losManager.canAddLOSCoverageFor(aDomainObjectContext)) {
      TLcyLOSDomainObject losDomainObject = losManager.addLOSCoverage(aDomainObjectContext);
    
      //Adjust the altitude mode
      //This requires a write lock on the los model, as it changes the LOS domain object
      ILcdModel losModel = losManager.getLOSModel(losDomainObject);
      try (TLcdLockUtil.Lock autoUnlock = TLcdLockUtil.writeLock(losModel)) {
        //Create properties with a different altitude mode
        TLspLOSProperties customProperties = new TLspLOSProperties(losDomainObject.getLOSProperties());
        customProperties.setCenterPointAltitudeMode(TLcdCoverageAltitudeMode.ABOVE_GROUND_LEVEL);
    
        //Install the custom properties on the domain object, and indicate to the model that the object has been changed
        losDomainObject.setLOSProperties(customProperties);
        losModel.elementChanged(losDomainObject, ILcdModel.FIRE_LATER);
      } finally {
        losModel.fireCollectedModelChanges();
      }
    }
  • When the user creates the LOS through the UI, you can use the same setLOSProperties method of the TLcyLOSDomainObject to update the properties each time a new TLcyLOSDomainObject is created. You can attach an ILcdModelListener to the LOS model to be informed when new LOS domain objects are created:

    //Create a model listener which listens for the creation of new objects
    ILcdModelListener modelListener = new ILcdModelListener() {
      @Override
      public void modelChanged(TLcdModelChangedEvent aEvent) {
        ILcdModel losModel = aEvent.getModel();
        try (TLcdLockUtil.Lock lock = TLcdLockUtil.readLock(losModel)) {
          Enumeration elements = aEvent.elements();
          while (elements.hasMoreElements()) {
            TLcyLOSDomainObject losDomainObject = (TLcyLOSDomainObject) elements.nextElement();
            int change = aEvent.retrieveChange(losDomainObject);
            if (change == TLcdModelChangedEvent.OBJECT_ADDED) {
              //Use an invokeLater. Changing the altitude mode will change the object, and requires the firing of an event
              //If we don't use an invokeLater, certain model listeners could receive the change event before the added event
              TLcdAWTUtil.invokeLater(new Runnable() {
                @Override
                public void run() {
                  try (TLcdLockUtil.Lock autoUnlock = TLcdLockUtil.writeLock(losModel)) {
                    //Create properties with a different altitude mode
                    TLspLOSProperties customProperties = new TLspLOSProperties(losDomainObject.getLOSProperties());
                    customProperties.setCenterPointAltitudeMode(TLcdCoverageAltitudeMode.ABOVE_GROUND_LEVEL);
    
                    //Install the custom properties on the domain object, and indicate to the model that the object has been changed
                    losDomainObject.setLOSProperties(customProperties);
                    losModel.elementChanged(losDomainObject, ILcdModel.FIRE_LATER);
                  } finally {
                    losModel.fireCollectedModelChanges();
                  }
                }
              });
    
            }
          }
        }
      }
    };
    
    //Keep track of all layers which are added and removed to the view
    ILspView view = aMapComponent.getMainView();
    //We're only interested in LOS model layers
    view.addLayeredListener(new ILcdLayeredListener() {
      @Override
      public void layeredStateChanged(TLcdLayeredEvent e) {
        ILcdLayer layer = e.getLayer();
        if (!Objects.equals(layer.getModel().getModelDescriptor().getTypeName(), TLcyLOSAddOn.LOS_MODEL_DESCRIPTOR_TYPENAME)) {
          //We're only interested in LOS model layers
          return;
        }
        if (e.getID() == TLcdLayeredEvent.LAYER_ADDED) {
          layer.getModel().addModelListener(modelListener);
        } else if (e.getID() == TLcdLayeredEvent.LAYER_REMOVED) {
          layer.getModel().removeModelListener(modelListener);
        }
      }
    }, true);