Class Measurement

  • All Implemented Interfaces:
    Cloneable

    public abstract class Measurement
    extends Object
    implements Cloneable

    The Measurement class can be used to attach real-world physical measurements to an image or a section of a page (or canvas). For example, a bitmap images may represent an architects drawing, or a satellite or microscope image. Associating a measurement with it will allow supporting PDF viewers to measure distances within that area on the page in real-world units, rather than the size of the image on the page. Measurements come in two types:

    • rectilinear, added in PDF 1.6 and used to measure distance between points, e.g. in meters
    • geospatial, added in PDF 2.0, where points are absolute locations on the earths surface

    A Measurement object applies to an area on the page, and within that area there is a mapping between page points, expressed in normal PDF units (points from the bottom-left of the page) to the units of the Measurement object. A page may have multiple Measurements (in theory they can even overlap) retrieved by calling PDFPage.getMeasurements(). Measurements may also be added to objects like a bitmap image with the PDFImage.setMeasurement(org.faceless.pdf2.Measurement) method - this specifies the intrinsic measurements of the image, which will be added to the list for any page that the image is drawn to.

    Rectilinear Measurements

    Rectilinear measurements are normally for measuring relative distance between points; it isn't an absolute measurement system. The full specification supports many options for archaic measurement systems which this API can read, and any Point2D returned from the fromPage(java.awt.geom.Point2D) method will be formatted to reflect this if you convert it to a String by calling point.toString(). However this API only supports creation of rectilinear measurements using the metric system. The units for any point can be retrieved by calling getScalarUnits(), and distances between points can be formatted using the measurement system by calling formatScalar(double). Here's an example:

      PDFImage image = ...
      image.setMeasurement(Measurement.createRectilinear(0.5, 0.5));    // 500mm x 500mm
      page.drawImage(image, 100, 100, 200, 200);  // Draw the image between 100,100 and 200,200
      Measurement m = page.getMeasurements().get(0);  // Retrieve the measurement from the page
      Point2D p1 = new Point2D.Double(100, 100);      // The bottom-left of the image
      Point2D p2 = new Point2D.Double(200, 100);      // The bottom-right of the image;
      p1 = m.fromPage(p1);                            // Convert those points to
      p2 = m.fromPage(p2);
      double d = p1.distance(p2);                     // The distance between p1 and p2 is 500mm
      System.out.println(d);                          // prints "500"
      System.out.println(m.getScalarUnits());         // prints "mm"
      System.out.println(m.formatScalar(d));          // prints "500mm", depending on the formatter
     

    Geospatial Measurements

    Geospatial measurements measure absolute points on the globe, and are new in PDF 2.0 - the GIS industry calls PDFs using these measurements Geospatial PDF. The core PDF library has limited support for Geospatial measurements, but requires the GeoTools API Jars in the classpath for full support. These Jars will be automatically detected if present, no API change is required. The differences are:

    • limited support - the API can recognise Geospatial Measurements, but may fail to convert between page units and geospatial units: the fromPage and toPage methods may return null. GeoTIFF images have no special support
    • full support - the API can convert between page units and geospatial units. When creating a PDFImage from a GeoTIFF source image, it will be created with a default Measurements object derived from the GeoTIFF data (note: GeoTools is only used to convert the metadata to a map projection; the bitmap data is still loaded with the PDF API).

    With Geotools support the full coordinate reference system of any embedded measurement is available. Here's an example showing how to print the latitude/longitude bounds of any embedded Geospatial measurement.

     import org.geotools.geometry.Position2D;
     import org.geotools.referencing.CRS;
     import org.geotools.api.referencing.crs.ProjectedCRS;
     import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
     import org.geotools.api.referencing.operation.MathTransform;
     import org.faceless.pdf2.*;
     import java.awt.geom.*;
    
     PDF pdf = new PDF(new PDFReader(...));
     for (PDFPage page : pdf.getPages()) {
       for (Measurement m : page.getMeasurements()) {
         if (m.getType().equals("geospatial")) {
           Rectangle2D bounds = m.getPageArea().getBounds2D();
           Point2D p0 = new Point2D.Double(bounds.getMinX(), bounds.getMinY());
           Point2D p1 = new Point2D.Double(bounds.getMaxX(), bounds.getMaxY());
           Position2D r0 = (Position2D)m.fromPage(p0);
           Position2D r1 = (Position2D)m.fromPage(p1);
           CoordinateReferenceSystem crs = r0.getCoordinateReferenceSystem();
           // Typically the CRS is a projected coordinate system, so "r0" and "r1"
           // specify Easting and Northing values. To convert to Latitude/Longitude:
           if (crs instanceof ProjectedCRS) {
             CoordinateReferenceSystem basecrs = ((ProjectedCRS)crs).getBaseCRS();
             MathTransform transform = CRS.findMathTransform(crs, basecrs);
             r0 = (Position2D)transform.transform(r0, new Position2D(basecrs));
             r1 = (Position2D)transform.transform(r1, new Position2D(basecrs));
           }
           // Points are now in latitude/longitude for a datum, eg WGS84.
           System.out.println("Geographic measurement from " + r0 + " to " + r1);
         }
       }
     }
     
    Since:
    2.29
    See Also:
    PDFPage.getMeasurements(), PDFImage.setMeasurement(org.faceless.pdf2.Measurement)
    • Constructor Detail

      • Measurement

        public Measurement()
    • Method Detail

      • isEmpty

        public boolean isEmpty()
        Return true if this measurement covers an empty area. Measurements are empty by default, and need to be derived before they are added to a page
      • derive

        public Measurement derive​(float x0,
                                  float y0,
                                  float x1,
                                  float y1)
        Return a copy of this Measurement with the page rectangle set to [x0, y0, x1, y1], suitable for adding a manual measurement directly to a page - although this is not the recommended way to do things, and it's generally easier to call PDFImage.setMeasurement(org.faceless.pdf2.Measurement) or PDFCanvas.setMeasurement(org.faceless.pdf2.Measurement) instead. But if necessary, here's how to do it.
          PDFPage page = ...
          Measurement m = Measurement.createRectilinear(1000, 1000);
          page.getMeasurements().add(m);                                                  // invalid
          page.getMeasurements().add(m.derive(0, 0, page.getWidth(), page.getHeight()));  // ok
         
        Returns:
        a copy of this Measurement which applies to the specified page area, or null if the area is empty or invalid
      • getType

        public abstract String getType()
        Return the type of measurement. Currently the only two defined values are rectilinear and geospatial
        Returns:
        the measurement type
      • getRectangle

        public float[] getRectangle()
        Return an array containing the bounds in which this measurement system applies. The returned array is a copy, and all values are measured in points from the bottom-left of the page.
        Returns:
        an array of four values [x0, y0, x1, y1] representing the lower-left and upper-right corners
      • getPageArea

        public Shape getPageArea()
        Return a Shape containing the bounds in which this measurement system applies. While in most cases this is the same Rectangle returned from getRectangle(), Geospatial Measurements may exclude parts of the area where the transform doesn't apply.
        Returns:
        a Shape representing the exact bounds in which this measurement applies, in points from the bottom-left of the page
      • getDescription

        public abstract String getDescription()
        Return a description of this Measurement scale. For Rectilinear measurements, this is a description of the scale. For Geospatial measurements, this is either the well known text of the Coordinate Reference System used by this Measurement, or a String of the format "EPSG:nnn"
        Returns:
        a description of this Measurement scale.
      • fromPage

        public abstract Point2D fromPage​(Point2D point)

        Convert a Point on the page to a Point in this Measurement, or null if the point is outside the bounds of this measurement area or otherwise cannot be converted.

        For Rectilinear measurements, the point's toString() method formats the value as specified by the Measurement, the point's values are in the units returned from getScalarUnits().

        For Geospatial measurements when GeoTools is used, the returned value can be cast to a org.geotools.geometry.Position2D to retrieve the details of how it is measured, including the CoordinateReferenceSystem.

        Parameters:
        point - a point in page units (i.e. measured in PostScript points, 1/72") from the bottom-left of the page.
        Returns:
        a point in the units of this Measurement object, or null if the point is out-of-bounds or cannot be converted.
      • toPage

        public abstract Point2D toPage​(Point2D point)

        Convert a Point in this Measurement's units to a point on the page, or null if it cannot be converted.

        For Rectilinear measurements, the points values are specified in the units returned from getScalarUnits(), and are measured from the Measurement's Origin Point (which can be retrieved by calling this method with a zero-valued point).

        For Geospatial measurements when GeoTools is used, if the Point is a org.geotools.geometry.Position2D with a Coordinate Reference System that CRS will be used, otherwise the point is assumed to be in the native Coordinate Reference System of this Measurement.

        Points that are out-of-bounds for this Measurement are still returned, to avoid issues with floating-point rounding. They should only be considered valid if they are within the shape returned from getPageArea()

        Parameters:
        point - a point in the units described
        Returns:
        a point in page units (i.e. measured in PostScript points, 1/72") from the bottom-left of the page, or null if it cannot be converted
      • getScalarUnits

        public abstract String getScalarUnits()
        Return the units in which Points returned from this class are measured. The exact meaning depends on the measurement system, but "m" for meters and "°" for degrees would be typical return values. If the scale uses different units for X and Y components, both values will be returned separated by a comma.
        Returns:
        the units, or an empty string if it cannot be determined.
      • formatScalar

        public String formatScalar​(double d)
        Format the supplied scalar value. Rectilinear measurements may have special formatting rules about how to display the value (eg with km/m/mm, or miles/furlongs/cubits), but the default formatting is simply appending the getScalarUnits() value after the number.
        Parameters:
        the - value to format
        Returns:
        the formatted scalar value
      • createRectilinear

        public static Measurement createRectilinear​(double width,
                                                    double height)
        Create a new rectilinear Measurement object, for measuring relative distance. Values are described in meters and formatted using an appropriate SI prefix, from pm to km.
        Parameters:
        width - the width of the measurement in meters
        height - the height of the measurement in meters
      • createGeospatial

        public static Measurement createGeospatial​(Point2D tl,
                                                   Point2D tr,
                                                   Point2D br,
                                                   Point2D bl,
                                                   String crs,
                                                   boolean projected)
        Create a new geospatial Measurement object based on four control points and a coordinate reference system. Positions supplied to this method have x=longitude (-ve is west) and y=latitude (-ve is south) Geotools is not required for this method to work, although the resulting Measurement will return null from toPage() and fromPage()
        Parameters:
        tl - the position at the top-left of the measurement area
        tr - the position at the top-right of the measurement area
        br - the position at the bottom-right of the measurement area
        bl - the position at the bottom-left of the measurement area
        crs - the Coordinate Reference System (CRS); either a string of the form "EPSG:nnn" to indicate an EPSG CRS, or a Well-Known Text.
        projected - whether the CRS is projected (true) or geographic (false)