github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/geo/geos/geos.go (about)

     1  // Copyright 2020 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  // Package geos is a wrapper around the spatial data types between the geo
    12  // package and the GEOS C library. The GEOS library is dynamically loaded
    13  // at init time.
    14  // Operations will error if the GEOS library was not found.
    15  package geos
    16  
    17  import (
    18  	"os"
    19  	"path/filepath"
    20  	"runtime"
    21  	"sync"
    22  	"unsafe"
    23  
    24  	"github.com/cockroachdb/cockroach/pkg/geo/geopb"
    25  	"github.com/cockroachdb/errors"
    26  )
    27  
    28  // #cgo CXXFLAGS: -std=c++14
    29  // #cgo !windows LDFLAGS: -ldl
    30  //
    31  // #include "geos.h"
    32  import "C"
    33  
    34  // EnsureInitErrorDisplay is used to control the error message displayed by
    35  // EnsureInit.
    36  type EnsureInitErrorDisplay int
    37  
    38  const (
    39  	// EnsureInitErrorDisplayPrivate displays the full error message, including
    40  	// path info. It is intended for log messages.
    41  	EnsureInitErrorDisplayPrivate EnsureInitErrorDisplay = iota
    42  	// EnsureInitErrorDisplayPublic displays a redacted error message, excluding
    43  	// path info. It is intended for errors to display for the client.
    44  	EnsureInitErrorDisplayPublic
    45  )
    46  
    47  // maxArrayLen is the maximum safe length for this architecture.
    48  const maxArrayLen = 1<<31 - 1
    49  
    50  // geosOnce contains the global instance of CR_GEOS, to be initialized
    51  // during at a maximum of once.
    52  // If it has failed to open, the error will be populated in "err".
    53  // This should only be touched by "fetchGEOSOrError".
    54  var geosOnce struct {
    55  	geos *C.CR_GEOS
    56  	loc  string
    57  	err  error
    58  	once sync.Once
    59  }
    60  
    61  // EnsureInit attempts to start GEOS if it has not been opened already
    62  // and returns the location if found, and an error if the CR_GEOS is not valid.
    63  func EnsureInit(errDisplay EnsureInitErrorDisplay, flagGEOSLocationValue string) (string, error) {
    64  	_, err := ensureInit(errDisplay, flagGEOSLocationValue)
    65  	return geosOnce.loc, err
    66  }
    67  
    68  // ensureInitInternal ensures initialization has been done, always displaying
    69  // errors privately and not assuming a flag has been set if initialized
    70  // for the first time.
    71  func ensureInitInternal() (*C.CR_GEOS, error) {
    72  	return ensureInit(EnsureInitErrorDisplayPrivate, "")
    73  }
    74  
    75  // ensureInits behaves as described in EnsureInit, but also returns the GEOS
    76  // C object which should be hidden from the public eye.
    77  func ensureInit(
    78  	errDisplay EnsureInitErrorDisplay, flagGEOSLocationValue string,
    79  ) (*C.CR_GEOS, error) {
    80  	geosOnce.once.Do(func() {
    81  		geosOnce.geos, geosOnce.loc, geosOnce.err = initGEOS(findGEOSLocations(flagGEOSLocationValue))
    82  	})
    83  	if geosOnce.err != nil && errDisplay == EnsureInitErrorDisplayPublic {
    84  		return nil, errors.Newf("geos: this operation is not available")
    85  	}
    86  	return geosOnce.geos, geosOnce.err
    87  }
    88  
    89  // findGEOSLocations returns the default locations where GEOS is installed.
    90  func findGEOSLocations(flagGEOSLocationValue string) []string {
    91  	var ext string
    92  	switch runtime.GOOS {
    93  	case "darwin":
    94  		ext = "dylib"
    95  	case "windows":
    96  		ext = "dll"
    97  	default:
    98  		ext = "so"
    99  	}
   100  	locs := []string{
   101  		filepath.Join(flagGEOSLocationValue, "libgeos_c."+ext),
   102  	}
   103  	// For CI, they are always in a parenting directory where libgeos_c is set.
   104  	// For now, this will need to look at every given location
   105  	// TODO(otan): fix CI to always use a fixed location OR initialize GEOS
   106  	// correctly for each test suite that may need GEOS.
   107  	locs = append(locs, findGEOSLocationsInParentingDirectories(ext)...)
   108  	return locs
   109  }
   110  
   111  // findGEOSLocationsInParentingDirectories attempts to find GEOS by looking at
   112  // parenting folders and looking inside `lib/lib_geos_c.*`.
   113  func findGEOSLocationsInParentingDirectories(ext string) []string {
   114  	locs := []string{}
   115  
   116  	// Add the CI path by trying to find all parenting paths and appending
   117  	// `lib/libgeos_c.<ext>`.
   118  	cwd, err := os.Getwd()
   119  	if err != nil {
   120  		panic(err)
   121  	}
   122  
   123  	for {
   124  		nextPath := filepath.Join(cwd, "lib", "libgeos_c."+ext)
   125  		if _, err := os.Stat(nextPath); err == nil {
   126  			locs = append(locs, nextPath)
   127  		}
   128  		nextCWD := filepath.Dir(cwd)
   129  		if nextCWD == cwd {
   130  			break
   131  		}
   132  		cwd = nextCWD
   133  	}
   134  	return locs
   135  }
   136  
   137  // initGEOS initializes the CR_GEOS by attempting to dlopen all
   138  // the paths as parsed in by locs.
   139  func initGEOS(locs []string) (*C.CR_GEOS, string, error) {
   140  	var err error
   141  	for _, loc := range locs {
   142  		var ret *C.CR_GEOS
   143  		errStr := C.CR_GEOS_Init(goToCSlice([]byte(loc)), &ret)
   144  		if errStr.data == nil {
   145  			return ret, loc, nil
   146  		}
   147  		err = errors.CombineErrors(
   148  			err,
   149  			errors.Newf(
   150  				"geos: cannot load GEOS from %s: %s",
   151  				loc,
   152  				string(cSliceToUnsafeGoBytes(errStr)),
   153  			),
   154  		)
   155  	}
   156  	if err != nil {
   157  		return nil, "", errors.Wrap(err, "geos: error during GEOS init")
   158  	}
   159  	return nil, "", errors.Newf("geos: no locations to init GEOS")
   160  }
   161  
   162  // goToCSlice returns a CR_GEOS_Slice from a given Go byte slice.
   163  func goToCSlice(b []byte) C.CR_GEOS_Slice {
   164  	if len(b) == 0 {
   165  		return C.CR_GEOS_Slice{data: nil, len: 0}
   166  	}
   167  	return C.CR_GEOS_Slice{
   168  		data: (*C.char)(unsafe.Pointer(&b[0])),
   169  		len:  C.size_t(len(b)),
   170  	}
   171  }
   172  
   173  // c{String,Slice}ToUnsafeGoBytes convert a CR_GEOS_{String,Slice} to a Go
   174  // byte slice that refer to the underlying C memory.
   175  func cStringToUnsafeGoBytes(s C.CR_GEOS_String) []byte {
   176  	return cToUnsafeGoBytes(s.data, s.len)
   177  }
   178  
   179  func cSliceToUnsafeGoBytes(s C.CR_GEOS_Slice) []byte {
   180  	return cToUnsafeGoBytes(s.data, s.len)
   181  }
   182  
   183  func cToUnsafeGoBytes(data *C.char, len C.size_t) []byte {
   184  	if data == nil {
   185  		return nil
   186  	}
   187  	// Interpret the C pointer as a pointer to a Go array, then slice.
   188  	return (*[maxArrayLen]byte)(unsafe.Pointer(data))[:len:len]
   189  }
   190  
   191  // cStringToSafeGoBytes converts a CR_GEOS_String to a Go byte slice.
   192  // Additionally, it frees the C memory.
   193  func cStringToSafeGoBytes(s C.CR_GEOS_String) []byte {
   194  	unsafeBytes := cStringToUnsafeGoBytes(s)
   195  	b := make([]byte, len(unsafeBytes))
   196  	copy(b, unsafeBytes)
   197  	C.free(unsafe.Pointer(s.data))
   198  	return b
   199  }
   200  
   201  // A Error wraps an error returned from a GEOS operation.
   202  type Error struct {
   203  	msg string
   204  }
   205  
   206  // Error implements the error interface.
   207  func (err *Error) Error() string {
   208  	return err.msg
   209  }
   210  
   211  func statusToError(s C.CR_GEOS_Status) error {
   212  	if s.data == nil {
   213  		return nil
   214  	}
   215  	return &Error{msg: string(cStringToSafeGoBytes(s))}
   216  }
   217  
   218  // WKTToEWKB parses a WKT into WKB using the GEOS library.
   219  func WKTToEWKB(wkt geopb.WKT, srid geopb.SRID) (geopb.EWKB, error) {
   220  	g, err := ensureInitInternal()
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  	var cEWKB C.CR_GEOS_String
   225  	if err := statusToError(C.CR_GEOS_WKTToEWKB(g, goToCSlice([]byte(wkt)), C.int(srid), &cEWKB)); err != nil {
   226  		return nil, err
   227  	}
   228  	return cStringToSafeGoBytes(cEWKB), nil
   229  }
   230  
   231  // BufferParamsJoinStyle maps to the GEOSBufJoinStyles enum in geos_c.h.in.
   232  type BufferParamsJoinStyle int
   233  
   234  // These should be kept in sync with the geos_c.h.in corresponding enum definition.
   235  const (
   236  	BufferParamsJoinStyleRound = 1
   237  	BufferParamsJoinStyleMitre = 2
   238  	BufferParamsJoinStyleBevel = 3
   239  )
   240  
   241  // BufferParamsEndCapStyle maps to the GEOSBufCapStyles enum in geos_c.h.in.
   242  type BufferParamsEndCapStyle int
   243  
   244  // These should be kept in sync with the geos_c.h.in corresponding enum definition.
   245  const (
   246  	BufferParamsEndCapStyleRound  = 1
   247  	BufferParamsEndCapStyleFlat   = 2
   248  	BufferParamsEndCapStyleSquare = 3
   249  )
   250  
   251  // BufferParams are parameters to provide into the GEOS buffer function.
   252  type BufferParams struct {
   253  	JoinStyle        BufferParamsJoinStyle
   254  	EndCapStyle      BufferParamsEndCapStyle
   255  	SingleSided      bool
   256  	QuadrantSegments int
   257  	MitreLimit       float64
   258  }
   259  
   260  // Buffer buffers the given geometry by the given distance and params.
   261  func Buffer(ewkb geopb.EWKB, params BufferParams, distance float64) (geopb.EWKB, error) {
   262  	g, err := ensureInitInternal()
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  	singleSided := 0
   267  	if params.SingleSided {
   268  		singleSided = 1
   269  	}
   270  	cParams := C.CR_GEOS_BufferParamsInput{
   271  		endCapStyle:      C.int(params.EndCapStyle),
   272  		joinStyle:        C.int(params.JoinStyle),
   273  		singleSided:      C.int(singleSided),
   274  		quadrantSegments: C.int(params.QuadrantSegments),
   275  		mitreLimit:       C.double(params.MitreLimit),
   276  	}
   277  	var cEWKB C.CR_GEOS_String
   278  	if err := statusToError(C.CR_GEOS_Buffer(g, goToCSlice(ewkb), cParams, C.double(distance), &cEWKB)); err != nil {
   279  		return nil, err
   280  	}
   281  	return cStringToSafeGoBytes(cEWKB), nil
   282  }
   283  
   284  // Area returns the area of an EWKB.
   285  func Area(ewkb geopb.EWKB) (float64, error) {
   286  	g, err := ensureInitInternal()
   287  	if err != nil {
   288  		return 0, err
   289  	}
   290  	var area C.double
   291  	if err := statusToError(C.CR_GEOS_Area(g, goToCSlice(ewkb), &area)); err != nil {
   292  		return 0, err
   293  	}
   294  	return float64(area), nil
   295  }
   296  
   297  // Length returns the length of an EWKB.
   298  func Length(ewkb geopb.EWKB) (float64, error) {
   299  	g, err := ensureInitInternal()
   300  	if err != nil {
   301  		return 0, err
   302  	}
   303  	var length C.double
   304  	if err := statusToError(C.CR_GEOS_Length(g, goToCSlice(ewkb), &length)); err != nil {
   305  		return 0, err
   306  	}
   307  	return float64(length), nil
   308  }
   309  
   310  // Centroid returns the centroid of an EWKB.
   311  func Centroid(ewkb geopb.EWKB) (geopb.EWKB, error) {
   312  	g, err := ensureInitInternal()
   313  	if err != nil {
   314  		return nil, err
   315  	}
   316  	var cEWKB C.CR_GEOS_String
   317  	if err := statusToError(C.CR_GEOS_Centroid(g, goToCSlice(ewkb), &cEWKB)); err != nil {
   318  		return nil, err
   319  	}
   320  	return cStringToSafeGoBytes(cEWKB), nil
   321  }
   322  
   323  // InterpolateLine returns the point along the given LineString which is at
   324  // a given distance from starting point.
   325  // Note: For distance less than 0 it returns start point similarly for distance
   326  // greater LineString's length.
   327  // InterpolateLine also works with (Multi)LineString. However, the result is
   328  // not appropriate as it combines all the LineString present in (MULTI)LineString,
   329  // considering all the corner points of LineString overlaps each other.
   330  func InterpolateLine(ewkb geopb.EWKB, distance float64) (geopb.EWKB, error) {
   331  	g, err := ensureInitInternal()
   332  	if err != nil {
   333  		return nil, err
   334  	}
   335  	var cEWKB C.CR_GEOS_String
   336  	if err := statusToError(C.CR_GEOS_Interpolate(g, goToCSlice(ewkb), C.double(distance), &cEWKB)); err != nil {
   337  		return nil, err
   338  	}
   339  	return cStringToSafeGoBytes(cEWKB), nil
   340  }
   341  
   342  // MinDistance returns the minimum distance between two EWKBs.
   343  func MinDistance(a geopb.EWKB, b geopb.EWKB) (float64, error) {
   344  	g, err := ensureInitInternal()
   345  	if err != nil {
   346  		return 0, err
   347  	}
   348  	var distance C.double
   349  	if err := statusToError(C.CR_GEOS_Distance(g, goToCSlice(a), goToCSlice(b), &distance)); err != nil {
   350  		return 0, err
   351  	}
   352  	return float64(distance), nil
   353  }
   354  
   355  // ClipEWKBByRect clips a EWKB to the specified rectangle.
   356  func ClipEWKBByRect(
   357  	ewkb geopb.EWKB, xMin float64, yMin float64, xMax float64, yMax float64,
   358  ) (geopb.EWKB, error) {
   359  	g, err := ensureInitInternal()
   360  	if err != nil {
   361  		return nil, err
   362  	}
   363  	var cEWKB C.CR_GEOS_String
   364  	if err := statusToError(C.CR_GEOS_ClipEWKBByRect(g, goToCSlice(ewkb), C.double(xMin),
   365  		C.double(yMin), C.double(xMax), C.double(yMax), &cEWKB)); err != nil {
   366  		return nil, err
   367  	}
   368  	return cStringToSafeGoBytes(cEWKB), nil
   369  }
   370  
   371  // Covers returns whether the EWKB provided by A covers the EWKB provided by B.
   372  func Covers(a geopb.EWKB, b geopb.EWKB) (bool, error) {
   373  	g, err := ensureInitInternal()
   374  	if err != nil {
   375  		return false, err
   376  	}
   377  	var ret C.char
   378  	if err := statusToError(C.CR_GEOS_Covers(g, goToCSlice(a), goToCSlice(b), &ret)); err != nil {
   379  		return false, err
   380  	}
   381  	return ret == 1, nil
   382  }
   383  
   384  // CoveredBy returns whether the EWKB provided by A is covered by the EWKB provided by B.
   385  func CoveredBy(a geopb.EWKB, b geopb.EWKB) (bool, error) {
   386  	g, err := ensureInitInternal()
   387  	if err != nil {
   388  		return false, err
   389  	}
   390  	var ret C.char
   391  	if err := statusToError(C.CR_GEOS_CoveredBy(g, goToCSlice(a), goToCSlice(b), &ret)); err != nil {
   392  		return false, err
   393  	}
   394  	return ret == 1, nil
   395  }
   396  
   397  // Contains returns whether the EWKB provided by A contains the EWKB provided by B.
   398  func Contains(a geopb.EWKB, b geopb.EWKB) (bool, error) {
   399  	g, err := ensureInitInternal()
   400  	if err != nil {
   401  		return false, err
   402  	}
   403  	var ret C.char
   404  	if err := statusToError(C.CR_GEOS_Contains(g, goToCSlice(a), goToCSlice(b), &ret)); err != nil {
   405  		return false, err
   406  	}
   407  	return ret == 1, nil
   408  }
   409  
   410  // Crosses returns whether the EWKB provided by A crosses the EWKB provided by B.
   411  func Crosses(a geopb.EWKB, b geopb.EWKB) (bool, error) {
   412  	g, err := ensureInitInternal()
   413  	if err != nil {
   414  		return false, err
   415  	}
   416  	var ret C.char
   417  	if err := statusToError(C.CR_GEOS_Crosses(g, goToCSlice(a), goToCSlice(b), &ret)); err != nil {
   418  		return false, err
   419  	}
   420  	return ret == 1, nil
   421  }
   422  
   423  // Equals returns whether the EWKB provided by A equals the EWKB provided by B.
   424  func Equals(a geopb.EWKB, b geopb.EWKB) (bool, error) {
   425  	g, err := ensureInitInternal()
   426  	if err != nil {
   427  		return false, err
   428  	}
   429  	var ret C.char
   430  	if err := statusToError(C.CR_GEOS_Equals(g, goToCSlice(a), goToCSlice(b), &ret)); err != nil {
   431  		return false, err
   432  	}
   433  	return ret == 1, nil
   434  }
   435  
   436  // Intersects returns whether the EWKB provided by A intersects the EWKB provided by B.
   437  func Intersects(a geopb.EWKB, b geopb.EWKB) (bool, error) {
   438  	g, err := ensureInitInternal()
   439  	if err != nil {
   440  		return false, err
   441  	}
   442  	var ret C.char
   443  	if err := statusToError(C.CR_GEOS_Intersects(g, goToCSlice(a), goToCSlice(b), &ret)); err != nil {
   444  		return false, err
   445  	}
   446  	return ret == 1, nil
   447  }
   448  
   449  // Overlaps returns whether the EWKB provided by A overlaps the EWKB provided by B.
   450  func Overlaps(a geopb.EWKB, b geopb.EWKB) (bool, error) {
   451  	g, err := ensureInitInternal()
   452  	if err != nil {
   453  		return false, err
   454  	}
   455  	var ret C.char
   456  	if err := statusToError(C.CR_GEOS_Overlaps(g, goToCSlice(a), goToCSlice(b), &ret)); err != nil {
   457  		return false, err
   458  	}
   459  	return ret == 1, nil
   460  }
   461  
   462  // Touches returns whether the EWKB provided by A touches the EWKB provided by B.
   463  func Touches(a geopb.EWKB, b geopb.EWKB) (bool, error) {
   464  	g, err := ensureInitInternal()
   465  	if err != nil {
   466  		return false, err
   467  	}
   468  	var ret C.char
   469  	if err := statusToError(C.CR_GEOS_Touches(g, goToCSlice(a), goToCSlice(b), &ret)); err != nil {
   470  		return false, err
   471  	}
   472  	return ret == 1, nil
   473  }
   474  
   475  // Within returns whether the EWKB provided by A is within the EWKB provided by B.
   476  func Within(a geopb.EWKB, b geopb.EWKB) (bool, error) {
   477  	g, err := ensureInitInternal()
   478  	if err != nil {
   479  		return false, err
   480  	}
   481  	var ret C.char
   482  	if err := statusToError(C.CR_GEOS_Within(g, goToCSlice(a), goToCSlice(b), &ret)); err != nil {
   483  		return false, err
   484  	}
   485  	return ret == 1, nil
   486  }
   487  
   488  //
   489  // DE-9IM related
   490  //
   491  
   492  // Relate returns the DE-9IM relation between A and B.
   493  func Relate(a geopb.EWKB, b geopb.EWKB) (string, error) {
   494  	g, err := ensureInitInternal()
   495  	if err != nil {
   496  		return "", err
   497  	}
   498  	var ret C.CR_GEOS_String
   499  	if err := statusToError(C.CR_GEOS_Relate(g, goToCSlice(a), goToCSlice(b), &ret)); err != nil {
   500  		return "", err
   501  	}
   502  	if ret.data == nil {
   503  		return "", errors.Newf("expected DE-9IM string but found nothing")
   504  	}
   505  	return string(cStringToSafeGoBytes(ret)), nil
   506  }
   507  
   508  // RelatePattern whether A and B have a DE-9IM relation matching the given pattern.
   509  func RelatePattern(a geopb.EWKB, b geopb.EWKB, pattern string) (bool, error) {
   510  	g, err := ensureInitInternal()
   511  	if err != nil {
   512  		return false, err
   513  	}
   514  	var ret C.char
   515  	if err := statusToError(
   516  		C.CR_GEOS_RelatePattern(g, goToCSlice(a), goToCSlice(b), goToCSlice([]byte(pattern)), &ret),
   517  	); err != nil {
   518  		return false, err
   519  	}
   520  	return ret == 1, nil
   521  }