Every member defined on an interface represents an added cost to whoever is ultimately going to implement it. Some of our interfaces have gotten way out of hand, and #30* seeks to help with that by segregating the interfaces more so that the implementer doesn't have to account for everything under the sun.
- *looking at just the original issue, not what the comments turned it into
However, we could get some "quick wins" towards usability by removing some members that are outright redundant with other members already on the same interface (adding extension methods in their place where appropriate to avoid harming usability).
I propose that we try to simplify as much of this list as we reasonably can...
ICoordinateBuffer
AddCoordinate(...)
is redundant with InsertCoordinate(Count, ...)
, but can we just get rid of this interface?
ICoordinateSequence
Ordinate
/ Ordinates
and friends... that's a whole other topic of its own.
GetCoordinateCopy(i)
is equivalent to GetCoordinate(i, CreateCoordinate())
.
ToCoordinateArray()
is a bunch of GetCoordinateCopy(i)
calls to initialize new Coordinate[Count]
.
ExpandEnvelope(env)
is a bunch of calls to env.ExpandToInclude(GetX(i), GetY(i))
.
ICoordinateSequenceFactory
Again, leaving out Ordinate
/ Ordinates
and friends.
ICoordinateSequence Create(int size, int dimension, int measures)
is all that implementations should need to care about implementing.
"Create it as a copy of this other thing" can have a major impact on performance for pretty much every implementation (there's almost always a better way to copy from an instance of the same type), however, I think we can satisfy that need almost as well with a new "default interface method"*:
public interface ICoordinateSequence
{
/* ... */
void CopyFrom(ICoordinateSequence other, int offset, int count, IEnumerable<Ordinate> ordinatesToCopy)
{
// not writing parameter validation here
var ordinatesToCopyArray = ordinatesToCopy.ToArray();
for (int i = 0; i < count; i++)
{
foreach (var ordinate in ordinatesToCopyArray)
{
this.SetOrdinate(i, ordinate, other.GetOrdinate(i + offset, ordinate));
}
}
}
}
*I know that we can't do default interface methods properly, but there's a way to cheat at it that's almost as good... instead of a true "default interface method", we can use an extension method with a helper interface... to give an idea, an interface with a "default method" that we wish we could write like this:
public interface ISampleInterface
{
void DoTheThing() => Console.WriteLine("I am doing the thing, but not very well");
}
could be delivered like this:
public interface ISampleInterface
{
// DoTheThing is *NOT* here.
}
public interface IDoTheThingBetter
{
void DoTheThing();
}
public static class SampleInterfaceExtensions
{
public static void DoTheThing(this ISampleInterface @this)
{
if (@this is IDoTheThingBetter thingDoer)
{
// "overridden" implementation
thingDoer.DoTheThing();
return;
}
// default implementation
Console.WriteLine("I am doing the thing, but not very well");
}
}
ICurve
IsClosed
is the same as checking whether StartPoint
and EndPoint
are equal.
IsRing
is apparently IsClosed && IsSimple
, according to its single implementation that isn't called by anyone I can discover right now.
IGeometry
Strict redundancies
SRID
is Factory.SRID
.
GeometryType
looks like GetType().Name
???
Envelope
is Factory.ToGeometry(EnvelopeInternal)
.
Normalized()
is Copy().Normalize()
+ return the normalized copy.
EqualsExact(g)
is EqualsExact(g, 0)
.
EqualsNormalized(g)
is Normalized().EqualsExact(g.Normalized())
.
PointOnSurface
is InteriorPoint
.
x.Within(y)
is y.Contains(x)
.
x.CoveredBy(y)
is y.Covers(x)
.
x.Disjoint(y)
is !x.Intersects(y)
.
Relate(g, somePattern)
is Relate(g).Matches(somePattern)
.
Buffer(double, IBufferParameters)
is the only Buffer
method we need.
- though I'd kinda like
IBufferParameters
not to be an interface...
Can be implemented via a call to Apply(...)
int NumGeometries
int NumPoints
Coordinate Coordinate
Coordinate[] Coordinates
double[] GetOrdinates(Ordinate)
Dimension Dimension
(but OgcGeometryType
will often be enough)
IGeometry Envelope
Envelope EnvelopeInternal
bool IsEmpty
Can be implemented via Relate(g)
, though there's often a decent way to short-circuit.
Contains
/ Within
Covers
/ CoveredBy
Intersects
/ Disjoint
Touches
Crosses
Overlaps
EqualsTopologically
IGeometryCollection
int Count
is NumGeometries
this[int]
is GetGeometryN
IGeometry[] Geometries
also could be this.ToArray()
if we're OK with no longer defining it as a view of the underlying data... not exactly a discussion for here though.
IGeometryFactory
These methods are variants of other methods invoke those other methods with some sort of "empty":
CreatePoint()
CreateLineString()
CreateLinearRing()
CreatePolygon()
CreatePolygon(ILinearRing)
CreateMultiPoint()
CreateMultiLineString()
CreateMultiPolygon()
CreateGeometryCollection()
These methods are variants of other ICoordinateSequence
methods via CoordinateSequenceFactory
:
CreatePoint(Coordinate)
CreateLineString(Coordinate[])
CreateLinearRing(Coordinate[])
CreatePolygon(Coordinate[])
CreateMultiPointFromCoords(Coordinate[])
Finally, CreatePolygon(ICoordinateSequence)
is CreatePolygon(CreateLineString(seq))
ILineString
GetCoordinateN(i)
is CoordinateSequence.GetCoordinate(i)
GetPointN(i)
is Factory.CreatePoint(GetCoordinateN(i))
IMultiCurve
bool IsClosed
is !IsEmpty && Cast<ICurve>().All(g => g.IsClosed)
IPoint
X
is CoordinateSequence.GetX(0)
. Same for Y, Z, M.
IPolygon
Same idea as in IGeometryCollection
, we only need either the array or the Num +
ExteriorRing
is Shell
.
ILineString[] InteriorRings
is Holes.ToArray<ILineString>()
int NumInteriorRings
/ GetInteriorRingN(int)
come from Holes
, or vice-versa if we don't want to keep defining Holes
as a view of the underlying data.
IPrecisionModel
bool IsFloating
, int MaximumSignificantDigits
, and void MakePrecise(Coordinate)
are all defined to be the same as their NTS implementations.
IGeometryServices
CreatePrecisionModel(IPrecisionModel)
is one of the two other overloads depending on the type.
All CreateGeometryFactory
overloads are equivalent to CreateGeometryFactory(IPrecisionModel, int, ICoordinateSequenceFactory)
, filling in the defaults from properties where missing.