ianmackenzie/elm-geometry/3.4.0

NOTE: the latest version is 4.0.0

Point2d

A Point2d represents a position in 2D space and is defined by its X and Y coordinates. This module contains a variety of point-related functionality, such as

  • Measuring distance between points
  • Scaling, rotating, translating, mirroring and projecting points
  • Converting points between different coordinate systems

Points are distinct from vectors but interact with them in well-defined ways; you can translate a point by a vector to result in a new point, or you can compute the vector from one point to another, but you cannot 'add' two points like you can add two vectors.

type alias Point2d units coordinates =
Point2d units coordinates

Constants

origin : Point2d units coordinates

The point with coordinates (0, 0).

Literals

unitless : Float -> Float -> Point2d Unitless coordinates

Construct a unitless Point2d value from its X and Y coordinates. See also fromUnitless.

The remaining functions all construct a Point2d from X and Y coordinates given in specific units. Functions like Point2d.xy are more useful in generic code, but these functions are useful for quickly creating hardcoded constant values, e.g.

point =
    Point2d.meters 2 3
meters : Float -> Float -> Point2d Meters coordinates
pixels : Float -> Float -> Point2d Pixels coordinates
millimeters : Float -> Float -> Point2d Meters coordinates
centimeters : Float -> Float -> Point2d Meters coordinates
inches : Float -> Float -> Point2d Meters coordinates
feet : Float -> Float -> Point2d Meters coordinates

Constructors

xy :
Quantity Float units
-> Quantity Float units
-> Point2d units coordinates

Construct a point from its X and Y coordinates.

point =
    Point2d.xy (Length.meters 2) (Length.meters 3)
Frame2d units globalCoordinates { defines : localCoordinates }
-> Quantity Float units
-> Quantity Float units
-> Point2d units globalCoordinates

Construct a point given its local coordinates within a particular frame:

rotatedFrame =
    Frame2d.atOrigin
        |> Frame2d.rotateBy (Angle.degrees 45)


Point2d.xyIn rotatedFrame
    (Length.meters 2)
    (Length.meters 0)
--> Point2d.meters 1.4142 1.4142
rTheta : Quantity Float units -> Angle -> Point2d units coordinates

Construct a point from a radius and angle. Radius is measured from the origin and angle is measured counterclockwise from the positive X direction.

Point2d.rTheta (Length.meters 2) (Angle.degrees 135)
--> Point2d.meters -1.4142 1.4142
Frame2d units globalCoordinates { defines : localCoordinates }
-> Quantity Float units
-> Angle
-> Point2d units globalCoordinates

Construct a point given its local polar coordinates within a particular frame:

localFrame =
    Frame2d.atPoint (Point2d.meters 2 1)


Point2d.rThetaIn localFrame
    (Length.meters 2)
    (Angle.degrees 45)
--> Point2d.meters 3.4142 2.4142
Point2d units coordinates
-> Point2d units coordinates
-> Point2d units coordinates

Construct a point halfway between two other points.

Point2d.midpoint
    (Point2d.meters 1 1)
    (Point2d.meters 3 7)
--> Point2d.meters 2 4
Point2d units coordinates
-> Point2d units coordinates
-> Float
-> Point2d units coordinates

Construct a point by interpolating from the first given point to the second, based on a parameter that ranges from zero to one.

startPoint =
    Point2d.origin


endPoint =
    Point2d.meters 8 12


Point2d.interpolateFrom startPoint endPoint 0.25
--> Point2d.meters 2 3

Partial application may be useful:

interpolatedPoint : Float -> Point2d
interpolatedPoint =
    Point2d.interpolateFrom startPoint endPoint


List.map interpolatedPoint [ 0, 0.5, 1 ]
--> [ Point2d.meters 0 0
--> , Point2d.meters 4 6
--> , Point2d.meters 8 12
--> ]

You can pass values less than zero or greater than one to extrapolate:

interpolatedPoint -0.5
--> Point2d.meters -4 -6


interpolatedPoint 1.25
--> Point2d.meters 10 15
Axis2d units coordinates
-> Quantity Float units
-> Point2d units coordinates

Construct a point along an axis at a particular distance from the axis' origin point.

Point2d.along Axis2d.y (Length.meters 3)
--> Point2d.meters 0 3

Positive and negative distances will be interpreted relative to the direction of the axis:

horizontalAxis =
    Axis2d.withDirection Direction2d.negativeX
        (Point2d.meters 1 1)


Point2d.along horizontalAxis (Length.meters 3)
--> Point2d.meters -2 1


Point2d.along horizontalAxis (Length.meters -3)
--> Point2d.meters 4 1
Point2d units coordinates
-> Point2d units coordinates
-> Point2d units coordinates
-> Maybe (Point2d units coordinates)

Attempt to find the circumcenter of three points; this is the center of the circle that passes through all three points. If the three given points are collinear, returns Nothing.

Point2d.circumcenter
    Point2d.origin
    (Point2d.meters 1 0)
    (Point2d.meters 0 1)
--> Just (Point2d.meters 0.5 0.5)


-- Ambiguous
Point2d.circumCenter
    Point2d.origin
    Point2d.origin
    (Point2d.meters 1 0)
--> Nothing


-- Impossible
Point2d.circumCenter
    Point2d.origin
    (Point2d.meters 2 0)
    (Point2d.meters 4 0)
--> Nothing

Interop

These functions are useful for interoperability with other Elm code that uses plain Float tuples or records to represent points.

(Float -> Quantity Float units)
-> ( Float, Float )
-> Point2d units coordinates

Construct a Point2d from a tuple of Float values, by specifying what units those values are in.

Point2d.fromTuple Length.meters ( 2, 3 )
--> Point2d.meters 2 3
(Quantity Float units -> Float)
-> Point2d units coordinates
-> ( Float, Float )

Convert a Point2d to a tuple of Float values, by specifying what units you want the result to be in.

point =
    Point2d.feet 2 3


Point2d.toTuple Length.inInches point
--> ( 24, 36 )
(Float -> Quantity Float units)
-> { x : Float
, y : Float
}
-> Point2d units coordinates

Construct a Point2d from a record with Float fields, by specifying what units those fields are in.

Point2d.fromRecord Length.inches { x = 24, y = 36 }
--> Point2d.inches 24 36
(Quantity Float units -> Float)
-> Point2d units coordinates
-> { x : Float
, y : Float
}

Convert a Point2d to a record with Float fields, by specifying what units you want the result to be in.

point =
    Point2d.meters 2 3


Point2d.toRecord Length.inCentimeters point
--> { x = 200, y = 300 }

Zero-copy conversions

These functions allow zero-overhead conversion of points to and from records with x and y Float fields, useful for efficient interop with other code that represents points as plain records.

{ x : Float
, y : Float
}
-> Point2d Meters coordinates
Point2d Meters coordinates
-> { x : Float
, y : Float
}
{ x : Float
, y : Float
}
-> Point2d Pixels coordinates
Point2d Pixels coordinates
-> { x : Float
, y : Float
}
{ x : Float
, y : Float
}
-> Point2d Unitless coordinates
Point2d Unitless coordinates
-> { x : Float
, y : Float
}

Properties

Point2d units coordinates
-> ( Quantity Float units, Quantity Float units )

Get the X and Y coordinates of a point as a tuple.

Point2d.coordinates (Point2d.meters 2 3)
--> ( Length.meters 2, Length.meters 3 )
xCoordinate : Point2d units coordinates -> Quantity Float units

Get the X coordinate of a point.

Point2d.xCoordinate (Point2d.meters 2 3)
--> Length.meters 2
yCoordinate : Point2d units coordinates -> Quantity Float units

Get the Y coordinate of a point.

Point2d.yCoordinate (Point2d.meters 2 3)
--> Length.meters 3
Frame2d units globalCoordinates { defines : localCoordinates }
-> Point2d units globalCoordinates
-> ( Quantity Float units, Quantity Float units )

Get the X and Y coordinates of a point relative to a given frame, as a tuple; these are the coordinates the point would have as viewed by an observer in that frame.

Frame2d units globalCoordinates { defines : localCoordinates }
-> Point2d units globalCoordinates
-> Quantity Float units

Find the X coordinate of a point relative to a given frame.

Frame2d units globalCoordinates { defines : localCoordinates }
-> Point2d units globalCoordinates
-> Quantity Float units

Find the Y coordinate of a point relative to a given frame.

Comparison

Quantity Float units
-> Point2d units coordinates
-> Point2d units coordinates
-> Bool

Compare two points within a tolerance. Returns true if the distance between the two given points is less than the given tolerance.

firstPoint =
    Point2d.meters 1 2


secondPoint =
    Point2d.meters 0.9999 2.0002


Point2d.equalWithin (Length.millimeters 1)
    firstPoint
    secondPoint
--> True


Point2d.equalWithin (Length.microns 1)
    firstPoint
    secondPoint
--> False
lexicographicComparison : Point2d units coordinates -> Point2d units coordinates -> Order

Compare two Point2d values lexicographically: first by X coordinate, then by Y. Can be used to provide a sort order for Point2d values.

Measurement

Point2d units coordinates
-> Point2d units coordinates
-> Quantity Float units

Find the distance from the first point to the second.

Point2d.distanceFrom
    (Point2d.meters 2 3)
    (Point2d.meters 5 7)
--> Length.meters 5

Partial application can be useful:

points =
    [ Point2d.meters 3 4
    , Point2d.meters 10 0
    , Point2d.meters -1 2
    ]


points
    |> Quantity.sortBy
        (Point2d.distanceFrom Point2d.origin)
--> [ Point2d.meters -1 2
--> , Point2d.meters 3 4
--> , Point2d.meters 10 0
--> ]
Axis2d units coordinates
-> Point2d units coordinates
-> Quantity Float units

Determine how far along an axis a particular point lies. Conceptually, the point is projected perpendicularly onto the axis, and then the distance of this projected point from the axis' origin point is measured. The result will be positive if the projected point is ahead the axis' origin point and negative if it is behind, with 'ahead' and 'behind' defined by the direction of the axis.

axis =
    Axis2d.withDirection Direction2d.x
        (Point2d.meters 1 2)


point =
    Point2d.meters 3 3


Point2d.signedDistanceAlong axis point
--> Length.meters 2


Point2d.signedDistanceAlong axis Point2d.origin
--> Length.meters -1
Axis2d units coordinates
-> Point2d units coordinates
-> Quantity Float units

Find the perpendicular distance of a point from an axis. The result will be positive if the point is to the left of the axis and negative if it is to the right, with the forwards direction defined by the direction of the axis.

-- A horizontal axis through a point with a Y
-- coordinate of 2 is effectively the line Y=2
axis =
    Axis2d.withDirection Direction2d.x
        (Point2d.meters 1 2)


point =
    Point2d.meters 3 3


-- Since the axis is in the positive X direction,
-- points above the axis are to the left (positive)
Point2d.signedDistanceFrom axis point
-->  Length.meters 1


-- and points below are to the right (negative)
Point2d.signedDistanceFrom axis Point2d.origin
--> Length.meters -2

This means that reversing an axis will also flip the sign of the result of this function:

-- Reversing an axis reverses its direction
reversedAxis =
    Axis2d.reverse axis


Point2d.signedDistanceFrom reversedAxis point
--> Length.meters -1


Point2d.signedDistanceFrom reversedAxis Point2d.origin
--> Length.meters 2

Transformations

Point2d units coordinates
-> Float
-> Point2d units coordinates
-> Point2d units coordinates

Perform a uniform scaling about the given center point. The center point is given first and the point to transform is given last. Points will contract or expand about the center point by the given scale. Scaling by a factor of 1 is a no-op, and scaling by a factor of 0 collapses all points to the center point.

centerPoint =
    Point2d.meters 1 1


point =
    Point2d.meters 2 3


Point2d.scaleAbout centerPoint 3 point
--> Point2d.meters 4 7


Point2d.scaleAbout centerPoint 0.5 point
--> Point2d.meters 1.5 2

Avoid scaling by a negative scaling factor - while this may sometimes do what you want it is confusing and error prone. Try a combination of mirror and/or rotation operations instead.

Point2d units coordinates
-> Angle
-> Point2d units coordinates
-> Point2d units coordinates

Rotate around a given center point counterclockwise by a given angle. The point to rotate around is given first and the point to rotate is given last.

centerPoint =
    Point2d.meters 2 0


angle =
    Angle.degrees 45


point =
    Point2d.meters 3 0


Point2d.rotateAround centerPoint angle point
--> Point2d.meters 2.7071 0.7071
Vector2d units coordinates
-> Point2d units coordinates
-> Point2d units coordinates

Translate a point by a given displacement.

point =
    Point2d.meters 3 4


displacement =
    Vector2d.meters 1 2


Point2d.translateBy displacement point
--> Point2d.meters 4 6

In more mathematical terms, this is 'point plus vector'. For 'point minus point' (giving the vector from one point to another), there is Vector2d.from.

Direction2d coordinates
-> Quantity Float units
-> Point2d units coordinates
-> Point2d units coordinates

Translate a point in a given direction by a given distance.

point =
    Point2d.meters 3 4


point
    |> Point2d.translateIn Direction2d.x
        (Length.meters 2)
--> Point2d.meters 5 4


point
    |> Point2d.translateIn Direction2d.y
        (Length.meters 2)
--> Point2d.meters 3 6


angledDirection =
    Direction2d.degrees 45


point
    |> Point2d.translateIn angledDirection
        (Length.meters 1)
--> Point2d.meters 3.7071 4.7071

The distance can be negative:

point
    |> Point2d.translateIn Direction2d.x
        (Length.meters -2)
--> Point2d.meters 1 4
Axis2d units coordinates
-> Point2d units coordinates
-> Point2d units coordinates

Mirror a point across an axis. The result will be the same distance from the axis but on the opposite side.

point =
    Point2d.meters 2 3


Point2d.mirrorAcross Axis2d.x point
--> Point2d.meters 2 -3


Point2d.mirrorAcross Axis2d.y point
--> Point2d.meters -2 3
Axis2d units coordinates
-> Point2d units coordinates
-> Point2d units coordinates

Project a point perpendicularly onto an axis.

point =
    Point2d.meters 2 3


Point2d.projectOnto Axis2d.x point
--> Point2d.meters 2 0


Point2d.projectOnto Axis2d.y point
--> Point2d.meters 0 3

The axis does not have to pass through the origin:

offsetYAxis =
    Axis2d.withDirection Direction2d.y
        (Point2d.meters 1 0)


Point2d.projectOnto offsetYAxis point
--> Point2d.meters 1 3

Unit conversions

at :
Quantity Float (Rate destinationUnits sourceUnits)
-> Point2d sourceUnits coordinates
-> Point2d destinationUnits coordinates

Convert a point from one units type to another, by providing a conversion factor given as a rate of change of destination units with respect to source units.

worldPoint =
    Point2d.meters 2 3


resolution : Quantity Float (Rate Pixels Meters)
resolution =
    Pixels.pixels 100 |> Quantity.per (Length.meters 1)


worldPoint |> Point2d.at resolution
--> Point2d.pixels 200 300
at_ :
Quantity Float (Rate sourceUnits destinationUnits)
-> Point2d sourceUnits coordinates
-> Point2d destinationUnits coordinates

Convert a point from one units type to another, by providing an 'inverse' conversion factor given as a rate of change of source units with respect to destination units.

screenPoint =
    Point2d.pixels 200 300


resolution : Quantity Float (Rate Pixels Meters)
resolution =
    Pixels.pixels 50 |> Quantity.per (Length.meters 1)


screenPoint |> Point2d.at_ resolution
--> Point2d.meters 4 6

Coordinate conversions

Frame2d units globalCoordinates { defines : localCoordinates }
-> Point2d units globalCoordinates
-> Point2d units localCoordinates

Take a point defined in global coordinates, and return it expressed in local coordinates relative to a given reference frame.

localFrame =
    Frame2d.atPoint (Point2d.meters 1 2)


Point2d.relativeTo localFrame (Point2d.meters 4 5)
--> Point2d.meters 3 3


Point2d.relativeTo localFrame (Point2d.meters 1 1)
--> Point2d.meters 0 -1
Frame2d units globalCoordinates { defines : localCoordinates }
-> Point2d units localCoordinates
-> Point2d units globalCoordinates

Take a point defined in local coordinates relative to a given reference frame, and return that point expressed in global coordinates.

localFrame =
    Frame2d.atPoint (Point2d.meters 1 2)


Point2d.placeIn localFrame (Point2d.meters 3 3)
--> Point2d.meters 4 5


Point2d.placeIn localFrame (Point2d.meters 0 1)
--> Point2d.meters 1 1

Centroid calculation

Point2d units coordinates
-> List (Point2d units coordinates)
-> Point2d units coordinates

Find the centroid (average) of one or more points, by passing the first point and then all remaining points. This allows this function to return a Point2d instead of a Maybe Point2d. You would generally use centroid within a case expression:

case points of
    [] ->
        -- some default behavior


    first :: rest ->
        let
            centroid =
                Point2d.centroid first rest
        in
        ...

Alternatively, you can use centroidN instead.

(a -> Point2d units coordinates)
-> a
-> List a
-> Point2d units coordinates

Like centroid, but lets you work with any kind of data as long as a point can be extracted/constructed from it. For example, to get the centroid of a bunch of vertices:

type alias Vertex =
    { position : Point2d Meters World
    , color : Color
    , id : Int
    }


vertexCentroid =
    Point2d.centroidOf .position
        firstVertex
        [ secondVertex
        , thirdVertex
        ]
Point2d units coordinates
-> Point2d units coordinates
-> Point2d units coordinates
-> Point2d units coordinates

Find the centroid of three points;

Point2d.centroid3d p1 p2 p3

is equivalent to

Point2d.centroid p1 [ p2, p3 ]

but is more efficient.

List (Point2d units coordinates)
-> Maybe (Point2d units coordinates)

Find the centroid of a list of N points. If the list is empty, returns Nothing. If you know you have at least one point, you can use centroid instead to avoid the Maybe.

Advanced

These functions are unsafe because they require you to track units manually. In general you should prefer other functions instead, but these functions may be useful when writing generic/library code.

{ x : Float
, y : Float
}
-> Point2d units coordinates

Construct a point from its raw X and Y coordinates as Float values. The values must be in whatever units the resulting point is considered to use (usually meters or pixels). You should generally use something safer such as meters, fromPixels, xy, fromRecord etc.

Point2d units coordinates
-> { x : Float
, y : Float
}

Extract a point's raw X and Y coordinates as Float values. These values will be in whatever units the point has (usually meters or pixels). You should generally use something safer such as toMeters, toRecord, xCoordinate etc.