Class Measurement
- java.lang.Object
-
- org.faceless.pdf2.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
Measurementobject 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 theMeasurementobject. A page may have multiple Measurements (in theory they can even overlap) retrieved by callingPDFPage.getMeasurements(). Measurements may also be added to objects like a bitmap image with thePDFImage.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
Point2Dreturned from thefromPage(java.awt.geom.Point2D)method will be formatted to reflect this if you convert it to a String by callingpoint.toString(). However this API only supports creation of rectilinear measurements using the metric system. The units for any point can be retrieved by callinggetScalarUnits(), and distances between points can be formatted using the measurement system by callingformatScalar(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
fromPageandtoPagemethods may returnnull. GeoTIFF images have no special support -
full support - the API can convert between page units and geospatial units. When creating
a
PDFImagefrom a GeoTIFF source image, it will be created with a defaultMeasurementsobject 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 Summary
Constructors Constructor Description Measurement()
-
Method Summary
All Methods Static Methods Instance Methods Abstract Methods Concrete Methods Modifier and Type Method Description static MeasurementcreateGeospatial(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.static MeasurementcreateRectilinear(double width, double height)Create a new rectilinear Measurement object, for measuring relative distance.Measurementderive(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 callPDFImage.setMeasurement(org.faceless.pdf2.Measurement)orPDFCanvas.setMeasurement(org.faceless.pdf2.Measurement)instead.StringformatScalar(double d)Format the supplied scalar value.abstract Point2DfromPage(Point2D point)Convert a Point on the page to a Point in this Measurement, ornullif the point is outside the bounds of this measurement area or otherwise cannot be converted.abstract StringgetDescription()Return a description of this Measurement scale.ShapegetPageArea()Return a Shape containing the bounds in which this measurement system applies.float[]getRectangle()Return an array containing the bounds in which this measurement system applies.abstract StringgetScalarUnits()Return the units in which Points returned from this class are measured.abstract StringgetType()Return the type of measurement.booleanisEmpty()Return true if this measurement covers an empty area.abstract Point2DtoPage(Point2D point)Convert a Point in this Measurement's units to a point on the page, ornullif it cannot be converted.
-
-
-
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 callPDFImage.setMeasurement(org.faceless.pdf2.Measurement)orPDFCanvas.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 arerectilinearandgeospatial- 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 fromgetRectangle(), 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
nullif 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 fromgetScalarUnits().For Geospatial measurements when GeoTools is used, the returned value can be cast to a
org.geotools.geometry.Position2Dto 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
nullif 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
nullif 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.Position2Dwith 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 thegetScalarUnits()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 metersheight- 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 fromtoPage()andfromPage()- Parameters:
tl- the position at the top-left of the measurement areatr- the position at the top-right of the measurement areabr- the position at the bottom-right of the measurement areabl- the position at the bottom-left of the measurement areacrs- 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)
-
-