Class MapLineItem

All Implemented Interfaces:
IFeedbackProvider, IDrawable, EventListener

public class MapLineItem extends AMapItem
A map-native line item defined by two geographic endpoints and rendered as a seam-aware great-circle route.

Conceptual model

Unlike edu.cnu.mdi.item.LineItem, which stores a straight segment in ordinary world (projection-space) coordinates, MapLineItem stores its geometry in geographic coordinates:

  • _startLatLon — geographic start point in radians (x = longitude, y = latitude)
  • _endLatLon — geographic end point in radians (x = longitude, y = latitude)

When drawn, the item is projected and sampled as a great-circle route through MapGraphics, giving the familiar "airline route" appearance on a map.

Interaction model

  • Creation — use the ordinary straight-line rubberband. The tool converts its two screen endpoints to geographic coordinates via MapContainer.localToLatLon(java.awt.Point, java.awt.geom.Point2D.Double) and constructs a MapLineItem.
  • Drag — moves both geographic endpoints by the great-circle displacement implied by the mouse movement, preserving great-circle length and initial bearing exactly.
  • Resize — replaces the selected endpoint with the current geographic cursor location.
  • Rotate — rotates both endpoints around the geodesic midpoint by the screen-space angle dragged, preserving great-circle length from the midpoint to each endpoint. See Rotation semantics below.

Focus and selection handles

The item focus is the exact geodesic midpoint of the arc (computed via MapGraphics.greatCircleEndPoint(java.awt.geom.Point2D.Double, double, double) at half the arc length). The two selection handles are the projected screen positions of the geographic endpoints.

Rotation semantics

Rotation is well-defined and physically meaningful for a great-circle line. The pivot is the geodesic midpoint (the item focus). The angular delta is the screen-space three-point angle dragged by the user (identical to how the base PathBasedItem handles rotation). The two half-lengths from the midpoint to each endpoint are preserved; only the bearings change.

Because a great-circle has two distinct initial bearings (from start→midpoint and from end→midpoint), both must be rotated independently using MapGraphics.greatCircleAzimuth(java.awt.geom.Point2D.Double, java.awt.geom.Point2D.Double) and MapGraphics.greatCircleEndPoint(java.awt.geom.Point2D.Double, double, double). This keeps the arc symmetric around its pivot for any rotation angle.

The AItem._azimuth field inherited from AItem is used as the cumulative screen-space rotation angle (in degrees) for display purposes (e.g. the rotation handle position). The geographic geometry is the authoritative state; _azimuth is purely a display aid that accumulates across multiple rotate gestures.

Geometry and caching

The projected route is cached as a MapGraphics.ProjectedMapShape after drawing. That cached shape is used for hit testing and bounds. The cache is cleared whenever the item is marked dirty.

Note on getWorldBounds()

The inherited AItem.getWorldBounds() contract requires a projection-space bounding rectangle. For a great-circle arc this rectangle is not computable without a projection reference, so this method returns a simple longitude/latitude bounding box of the two endpoints. This is conservative: it may miss the arc's apex if the arc bulges significantly above or below the chord (e.g. a trans-polar route). All visibility and hit-testing that matters uses the screen-space getBounds(IContainer) path through the cached projected shape, which is exact.

  • Field Details

  • Constructor Details

    • MapLineItem

      public MapLineItem(Layer layer, Point2D.Double startLatLon, Point2D.Double endLatLon)
      Creates a map-native great-circle line item.
      Parameters:
      layer - the layer this item lives on; must not be null
      startLatLon - geographic start point in radians (x = longitude, y = latitude)
      endLatLon - geographic end point in radians (x = longitude, y = latitude)
    • MapLineItem

      public MapLineItem(Layer layer, Point2D.Double startLatLon, Point2D.Double endLatLon, Object... keyVals)
      Creates a map-native great-circle line item with optional item property key/value pairs.
      Parameters:
      layer - the layer this item lives on; must not be null
      startLatLon - geographic start point in radians
      endLatLon - geographic end point in radians
      keyVals - optional PropertyUtils key/value pairs
  • Method Details

    • getStartLatLon

      public Point2D.Double getStartLatLon()
      Returns a defensive copy of the geographic start endpoint.
      Returns:
      the start point in radians; never null after construction
    • getEndLatLon

      public Point2D.Double getEndLatLon()
      Returns a defensive copy of the geographic end endpoint.
      Returns:
      the end point in radians; never null after construction
    • setStartLatLon

      public void setStartLatLon(Point2D.Double startLatLon)
      Replaces the geographic start endpoint and marks the item dirty.
      Parameters:
      startLatLon - the new start point in radians
    • setEndLatLon

      public void setEndLatLon(Point2D.Double endLatLon)
      Replaces the geographic end endpoint and marks the item dirty.
      Parameters:
      endLatLon - the new end point in radians
    • drawItem

      public void drawItem(Graphics2D g2, IContainer container)
      Draws this item as a seam-aware great-circle route.

      The rendered path is built through MapGraphics and cached in _projectedShape for subsequent hit-testing and bounds queries.

      Specified by:
      drawItem in class AItem
      Parameters:
      g2 - the graphics context
      container - the rendering container; must be a MapContainer
    • shouldDraw

      public boolean shouldDraw(Graphics2D g2, IContainer container)
      Returns true if this item's projected pixel bounds intersect the visible component area.
      Specified by:
      shouldDraw in class AItem
      Parameters:
      g2 - the graphics context (not used directly)
      container - the rendering container
      Returns:
      true if the route is (at least partially) visible
    • contains

      public boolean contains(IContainer container, Point screenPoint)
      Tests whether the rendered route or its selection handles contain the given screen point.

      Handle hit-testing is checked first so endpoint handles remain easy to select. If no handle is hit, the test falls through to a pixel proximity check against the projected great-circle route.

      Overrides:
      contains in class AItem
      Parameters:
      container - the rendering container
      screenPoint - the device-space point to test
      Returns:
      true if the point is near the route or on a handle
    • getBounds

      public Rectangle getBounds(IContainer container)
      Returns the device-space bounds of the projected route.
      Overrides:
      getBounds in class AItem
      Parameters:
      container - the rendering container
      Returns:
      the pixel-space bounding rectangle, or null if unavailable or empty
    • getSelectionPoints

      public Point[] getSelectionPoints(IContainer container)
      Returns the two endpoint handle positions in device coordinates.

      Unlike a polyline, a map line exposes only its two geographic endpoints as resize handles, even though the rendered route between them is curved.

      Overrides:
      getSelectionPoints in class AItem
      Parameters:
      container - the rendering container
      Returns:
      the two endpoint handle positions, or null if either endpoint cannot be projected
    • getRotatePoint

      public Point getRotatePoint(IContainer container)
      Returns the rotation handle position in device coordinates.

      The handle is placed at the projected screen position of the geodesic midpoint (the item focus), which is the pivot around which rotation occurs. This gives the user a natural handle: grab it and drag in an arc around the line's midpoint.

      Overrides:
      getRotatePoint in class AItem
      Parameters:
      container - the rendering container
      Returns:
      the screen position of the rotation handle, or null if the focus cannot be projected
    • modify

      public void modify()
      Continues an interactive modification.

      Drag

      Moves the start endpoint by the great-circle displacement between the drag start and current mouse positions (converted to geographic coordinates). The end endpoint is then recomputed from the new start using the preserved great-circle azimuth and length, keeping the arc's shape invariant across latitudes.

      Resize

      Replaces the selected endpoint (index 0 = start, 1 = end) with the current geographic cursor location. The opposite endpoint is unchanged.

      Rotate

      Rotates both endpoints around the geodesic midpoint (the item focus) by the screen-space three-point angle dragged by the user. Both half-arc lengths (midpoint→start and midpoint→end) are preserved; only the bearings change. The AItem._azimuth field accumulates the total screen-space rotation for display purposes.

      Specified by:
      modify in class AItem
    • getWorldBounds

      public Rectangle2D.Double getWorldBounds()
      Returns an approximate world-space bounding rectangle.

      This returns a simple longitude/latitude bounding box of the two endpoints. It is provided only to satisfy the AItem contract. It may under-estimate the true extent of high-latitude arcs that bulge significantly above or below the chord between the endpoints. All visibility and hit-testing that matters uses the exact screen-space bounds available through getBounds(IContainer).

      Specified by:
      getWorldBounds in class AItem
      Returns:
      a lon/lat bounding rectangle in radians, or null if either endpoint is absent
    • translateWorld

      public void translateWorld(double dx, double dy)
      Translates the item by raw longitude and latitude offsets in radians.

      This is a pragmatic translation: for small distances and mid-latitude items it works well. At high latitudes the longitudinal scale diverges from the latitudinal scale, so a northward mouse drag may introduce a small longitudinal error. A future implementation could convert the pixel delta to a geodesic bearing and length, then call MapGraphics.greatCircleEndPoint(java.awt.geom.Point2D.Double, double, double), but that requires a MapContainer reference not available here.

      Specified by:
      translateWorld in class AItem
      Parameters:
      dx - longitude offset in radians
      dy - latitude offset in radians
    • getFeedbackStrings

      public void getFeedbackStrings(IContainer container, Point pp, Point2D.Double wp, List<String> feedbackStrings)
      Provides mouse-over feedback showing the great-circle length and the geographic coordinates of both endpoints.
      Specified by:
      getFeedbackStrings in interface IFeedbackProvider
      Overrides:
      getFeedbackStrings in class AItem
      Parameters:
      container - the rendering container
      pp - current mouse position in device coordinates
      wp - current mouse position in container world coordinates (not used here)
      feedbackStrings - the list to populate
    • updateFocus

      protected void updateFocus()
      Recomputes the item focus as the exact geodesic midpoint of the arc.

      The midpoint is obtained by projecting half the total great-circle arc length along the initial bearing from the start endpoint.

      Overrides:
      updateFocus in class AMapItem
    • setDirty

      public void setDirty(boolean dirty)
      Marks the item dirty and clears the cached projected route.
      Specified by:
      setDirty in interface IDrawable
      Overrides:
      setDirty in class AItem
      Parameters:
      dirty - whether the item should be marked dirty
    • prepareForRemoval

      public void prepareForRemoval()
      Prepares this item for removal, releasing geographic geometry references.
      Specified by:
      prepareForRemoval in interface IDrawable
      Overrides:
      prepareForRemoval in class AItem
    • ensureProjectedShape

      protected void ensureProjectedShape(IContainer container)
      Ensures that _projectedShape is populated, building it from the current geographic endpoints if necessary.
      Parameters:
      container - the rendering container
    • projectLatLon

      protected Point projectLatLon(MapContainer mc, Point2D.Double latLon)
      Projects a geographic point (x = longitude, y = latitude in radians) to a device-space pixel coordinate.

      Returns null if the point projects to a non-finite coordinate (e.g. the point is on the far hemisphere of an orthographic projection).

      Parameters:
      mc - the map container
      latLon - geographic point in radians
      Returns:
      the device-space point, or null if projection fails
    • localToLatLon

      protected Point2D.Double localToLatLon(MapContainer mc, Point p)
      Converts a device-space point to a geographic longitude/latitude in radians.
      Parameters:
      mc - the map container
      p - the device-space point
      Returns:
      the geographic point in radians, or null if the container or point is null
    • copy

      protected static Point2D.Double copy(Point2D.Double p)
      Returns a defensive copy of a geographic point.
      Parameters:
      p - the source point; may be null
      Returns:
      a copied point, or null if the source is null
    • startModification

      public void startModification()
      Classify and begin a drag, resize, or rotate modification.

      Priority: rotate (if start point is in rotation handle) → resize (if start point is in a selection handle) → drag (otherwise).

      This method is a no-op if AItem._modification is null.

      Overridden to attach the MapLineItem.MapLineStartState snapshot with the current screen-space azimuth baked in, so that sequential rotate gestures accumulate correctly.

      Overrides:
      startModification in class AItem