Commit 82f172ab authored by craig[bot]'s avatar craig[bot]

Merge #47513 #49779 #49783 #49804

47513: ui: CSS modules for PlanView component r=koorosh a=koorosh

Depends on: #47484
Related to: #47527

- Refactored font imports to correctly resolve paths when module is required from different locations;
Fonts are imported directly from `app.styl` file which allows import `typography.styl` without dependencies. This change was required because importing `typography.styl` file from CSS modules
failed with unresolved paths inside of `fonts.styl` file (which was required in `typography.styl` file).
Before,
```
app.styl
|-- typography.styl
     |-- fonts.styl
```
Now:
```
app.styl
|-- typography.styl
|-- fonts.styl
```
- Move all files related to PlanView component under `planView` directory
- Added storybook for `PlanView` component
- `planView.module.styl` file contains copy of styles (from `statements.styl`) which is used by component only.

Release note: None

49779: opt: incorporate cast volatility r=RaduBerinde a=RaduBerinde

This change incorporates the new cast volatility information into the
VolatilitySet property.

Release note: None

49783: geo/geotransform: implement ST_Transform r=sumeerbhola a=otan

This PR implements ST_Transform, allowing the transformation from one
SRID to another.

The `geoprojbase` package defines a barebones set of types as well as a
hardcoded list of SRIDs to keep in memory. I've only filled in a few for
now, and will save updating this for a later PR.

`geoproj` is strictly a C library interface library which performs the
necessary transformations.

`geotransform` is where the function is actually handled and to be used
by `geo_builtins.go`.

Resolves #49055
Resolves #49056
Resolves #49057
Resolves #49058

Release note (sql change): Implemented the ST_Transform function for
geometries.

49804: opt: ON UPDATE cascades for Upsert r=RaduBerinde a=RaduBerinde

This change implements ON UPDATE actions for Upsert operations. The existing
machinery for Update can be used without modification.

Release note: None
Co-authored-by: default avatarAndrii Vorobiov <[email protected]>
Co-authored-by: default avatarRadu Berinde <[email protected]>
Co-authored-by: default avatarOliver Tan <[email protected]>
......@@ -927,7 +927,7 @@ BUILD_TAGGED_RELEASE =
## Override for .buildinfo/tag
BUILDINFO_TAG :=
$(go-targets): bin/.bootstrap $(BUILDINFO) $(CGO_FLAGS_FILES) $(PROTOBUF_TARGETS)
$(go-targets): bin/.bootstrap $(BUILDINFO) $(CGO_FLAGS_FILES) $(PROTOBUF_TARGETS) $(LIBPROJ)
$(go-targets): $(SQLPARSER_TARGETS) $(EXECGEN_TARGETS) $(OPTGEN_TARGETS)
$(go-targets): override LINKFLAGS += \
-X "github.com/cockroachdb/cockroach/pkg/build.tag=$(if $(BUILDINFO_TAG),$(BUILDINFO_TAG),$(shell cat .buildinfo/tag))" \
......@@ -1715,7 +1715,7 @@ logictest-bins := bin/logictest bin/logictestopt bin/logictestccl
# Additional dependencies for binaries that depend on generated code.
#
# TODO(benesch): Derive this automatically. This is getting out of hand.
bin/workload bin/docgen bin/execgen bin/roachtest $(logictest-bins): $(SQLPARSER_TARGETS) $(PROTOBUF_TARGETS)
bin/workload bin/docgen bin/execgen bin/roachtest $(logictest-bins): $(LIBPROJ) $(CGO_FLAGS_FILES) $(SQLPARSER_TARGETS) $(PROTOBUF_TARGETS)
bin/workload bin/roachtest $(logictest-bins): $(EXECGEN_TARGETS)
bin/roachtest $(logictest-bins): $(C_LIBS_CCL) $(CGO_FLAGS_FILES) $(OPTGEN_TARGETS)
......
......@@ -1148,6 +1148,18 @@ given Geometry.</p>
<p>This function utilizes the GEOS module.</p>
<p>This function will automatically use any available index.</p>
</span></td></tr>
<tr><td><a name="st_transform"></a><code>st_transform(geometry: geometry, from_proj_text: <a href="string.html">string</a>, srid: <a href="int.html">int</a>) &rarr; geometry</code></td><td><span class="funcdesc"><p>Transforms a geometry into the coordinate reference system assuming the from_proj_text to the new to_proj_text by projecting its coordinates. The supplied SRID is set on the new geometry.</p>
<p>This function utilizes the PROJ library for coordinate projections.</p>
</span></td></tr>
<tr><td><a name="st_transform"></a><code>st_transform(geometry: geometry, from_proj_text: <a href="string.html">string</a>, to_proj_text: <a href="string.html">string</a>) &rarr; geometry</code></td><td><span class="funcdesc"><p>Transforms a geometry into the coordinate reference system assuming the from_proj_text to the new to_proj_text by projecting its coordinates.</p>
<p>This function utilizes the PROJ library for coordinate projections.</p>
</span></td></tr>
<tr><td><a name="st_transform"></a><code>st_transform(geometry: geometry, srid: <a href="int.html">int</a>) &rarr; geometry</code></td><td><span class="funcdesc"><p>Transforms a geometry into the given SRID coordinate reference system by projecting its coordinates.</p>
<p>This function utilizes the PROJ library for coordinate projections.</p>
</span></td></tr>
<tr><td><a name="st_transform"></a><code>st_transform(geometry: geometry, to_proj_text: <a href="string.html">string</a>) &rarr; geometry</code></td><td><span class="funcdesc"><p>Transforms a geometry into the coordinate reference system referenced by the projection text by projecting its coordinates.</p>
<p>This function utilizes the PROJ library for coordinate projections.</p>
</span></td></tr>
<tr><td><a name="st_within"></a><code>st_within(geometry_a: geometry, geometry_b: geometry) &rarr; <a href="bool.html">bool</a></code></td><td><span class="funcdesc"><p>Returns true if geometry_a is completely inside geometry_b.</p>
<p>This function utilizes the GEOS module.</p>
<p>This function will automatically use any available index.</p>
......
---
Language: Cpp
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: false
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 100
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
- Regex: '^(<|"(gtest|isl|json)/)'
Priority: 3
- Regex: '^".*"$'
Priority: 4
- Regex: '.*'
Priority: 5
IncludeIsMainRegex: '(Test)?$'
IndentCaseLabels: false
IndentWidth: 2
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Left
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
TabWidth: 8
UseTab: Never
...
......@@ -18,28 +18,55 @@ package geoproj
// #cgo windows LDFLAGS: -lshlwapi -lrpcrt4
//
// #include "proj.h"
// #include <proj_api.h>
import "C"
import "unsafe"
import (
"unsafe"
// ProjPJ is the projPJ wrapper around the PROJ library's projPJ object.
type ProjPJ struct {
projPJ C.projPJ
}
"github.com/cockroachdb/cockroach/pkg/geo/geoprojbase"
"github.com/cockroachdb/errors"
)
// maxArrayLen is the maximum safe length for this architecture.
const maxArrayLen = 1<<31 - 1
// IsLatLng returns whether the underlying ProjPJ is a latlng system.
// TODO(otan): store this metadata in the projPJ struct.
func (p *ProjPJ) IsLatLng() bool {
return C.pj_is_latlong(p.projPJ) != 0
func cStatusToUnsafeGoBytes(s C.CR_PROJ_Status) []byte {
if s.data == nil {
return nil
}
// Interpret the C pointer as a pointer to a Go array, then slice.
return (*[maxArrayLen]byte)(unsafe.Pointer(s.data))[:s.len:s.len]
}
// NewProjPJFromText initializes a ProjPJ from text.
// TODO(otan): thread through thread contexts and retrieve error messages.
// TODO(otan): use slice management mechanisms.
// TODO(otan): free after creation.
func NewProjPJFromText(proj4text string) (*ProjPJ, error) {
str := C.CString(proj4text)
defer C.free(unsafe.Pointer(str))
projPJ := C.pj_init_plus(str)
return &ProjPJ{projPJ: projPJ}, nil
// Project projects the given xCoords, yCoords and zCoords from one
// coordinate system to another using proj4text.
// Array elements are edited in place.
func Project(
from geoprojbase.Proj4Text,
to geoprojbase.Proj4Text,
xCoords []float64,
yCoords []float64,
zCoords []float64,
) error {
if len(xCoords) != len(yCoords) || len(xCoords) != len(zCoords) {
return errors.Newf(
"len(xCoords) != len(yCoords) != len(zCoords): %d != %d != %d",
len(xCoords),
len(yCoords),
len(zCoords),
)
}
if len(xCoords) == 0 {
return nil
}
if err := cStatusToUnsafeGoBytes(C.CR_PROJ_Transform(
(*C.char)(unsafe.Pointer(&from.Bytes()[0])),
(*C.char)(unsafe.Pointer(&to.Bytes()[0])),
C.long(len(xCoords)),
(*C.double)(unsafe.Pointer(&xCoords[0])),
(*C.double)(unsafe.Pointer(&yCoords[0])),
(*C.double)(unsafe.Pointer(&zCoords[0])),
)); err != nil {
return errors.Newf("error from PROJ: %s", string(err))
}
return nil
}
......@@ -13,11 +13,64 @@ package geoproj
import (
"testing"
"github.com/cockroachdb/cockroach/pkg/geo/geoprojbase"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewProjPJFromText(t *testing.T) {
pjProj, err := NewProjPJFromText("+proj=longlat +datum=WGS84 +no_defs")
require.NoError(t, err)
require.True(t, pjProj.IsLatLng())
func TestProject(t *testing.T) {
testCases := []struct {
desc string
from geoprojbase.Proj4Text
to geoprojbase.Proj4Text
xCoords []float64
yCoords []float64
zCoords []float64
expectedXCoords []float64
expectedYCoords []float64
// Ignore Z Coord for now; it usually has a garbage value.
}{
{
desc: "SRID 4326 to 3857",
from: geoprojbase.MakeProj4Text("+proj=longlat +datum=WGS84 +no_defs"),
to: geoprojbase.MakeProj4Text("+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m [email protected] +wktext +no_defs"),
xCoords: []float64{1},
yCoords: []float64{1},
zCoords: []float64{0},
expectedXCoords: []float64{111319.490793274},
expectedYCoords: []float64{111325.142866385},
},
{
desc: "SRID 3857 to 4326",
from: geoprojbase.MakeProj4Text("+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m [email protected] +wktext +no_defs"),
to: geoprojbase.MakeProj4Text("+proj=longlat +datum=WGS84 +no_defs"),
xCoords: []float64{1},
yCoords: []float64{1},
zCoords: []float64{0},
expectedXCoords: []float64{0.000008983152841},
expectedYCoords: []float64{0.000008983152841},
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
err := Project(tc.from, tc.to, tc.xCoords, tc.yCoords, tc.zCoords)
require.NoError(t, err)
assert.InEpsilonSlicef(t, tc.expectedXCoords, tc.xCoords, 1e-10, "expected: %#v, found %#v", tc.expectedXCoords, tc.xCoords)
assert.InEpsilonSlicef(t, tc.expectedYCoords, tc.yCoords, 1e-10, "expected: %#v, found %#v", tc.expectedYCoords, tc.yCoords)
})
}
t.Run("test error handling", func(t *testing.T) {
err := Project(
geoprojbase.MakeProj4Text("+bad"),
geoprojbase.MakeProj4Text("+bad"),
[]float64{1},
[]float64{2},
[]float64{3},
)
require.Error(t, err)
})
}
......@@ -9,4 +9,62 @@
// licenses/APL.txt.
#include "proj.h"
#include <cstring>
#include <proj_api.h>
#include <stdlib.h>
#include <string>
const char* DEFAULT_ERROR_MSG = "PROJ could not parse proj4text";
namespace {
CR_PROJ_Status CR_PROJ_ErrorFromErrorCode(int code) {
char* err = pj_strerrno(code);
if (err == nullptr) {
err = (char*)DEFAULT_ERROR_MSG;
}
return {.data = err, .len = strlen(err)};
}
} // namespace
CR_PROJ_Status CR_PROJ_Transform(char* fromSpec, char* toSpec, long point_count, double* x,
double* y, double* z) {
CR_PROJ_Status err = {.data = NULL, .len = 0};
auto ctx = pj_ctx_alloc();
auto fromPJ = pj_init_plus_ctx(ctx, fromSpec);
if (fromPJ == nullptr) {
err = CR_PROJ_ErrorFromErrorCode(pj_ctx_get_errno(ctx));
pj_ctx_free(ctx);
return err;
}
auto toPJ = pj_init_plus_ctx(ctx, toSpec);
if (toPJ == nullptr) {
err = CR_PROJ_ErrorFromErrorCode(pj_ctx_get_errno(ctx));
pj_ctx_free(ctx);
return err;
}
// If we have a latlng from, transform to radians.
if (pj_is_latlong(fromPJ)) {
for (auto i = 0; i < point_count; i++) {
x[i] = x[i] * DEG_TO_RAD;
y[i] = y[i] * DEG_TO_RAD;
}
}
pj_transform(fromPJ, toPJ, point_count, 0, x, y, z);
int errCode = pj_ctx_get_errno(ctx);
if (errCode != 0) {
err = CR_PROJ_ErrorFromErrorCode(errCode);
pj_ctx_free(ctx);
return err;
}
// If we have a latlng to, transform to degrees.
if (pj_is_latlong(toPJ)) {
for (auto i = 0; i < point_count; i++) {
x[i] = x[i] * RAD_TO_DEG;
y[i] = y[i] * RAD_TO_DEG;
}
}
pj_ctx_free(ctx);
return err;
}
......@@ -7,3 +7,28 @@
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
// CR_PROJ_Slice contains data that does not need to be freed.
// // It can be either a Go or C pointer (which indicates who allocated the
// memory).
typedef struct {
char* data;
size_t len;
} CR_PROJ_Slice;
typedef CR_PROJ_Slice CR_PROJ_Status;
// CR_PROJ_Transform converts the given x/y/z coordinates to a new project specification.
// Note points (x[i], y[i], z[i]) are in the range 0 <= i < point_coint.
CR_PROJ_Status CR_PROJ_Transform(char* fromSpec, char* toSpec, long point_count, double* x,
double* y, double* z);
#ifdef __cplusplus
} // extern "C"
#endif
// Copyright 2020 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
// Package geoprojbase is a minimal dependency package that contains
// basic metadata and data structures for SRIDs and their CRS
// transofmrations.
package geoprojbase
import (
"bytes"
"github.com/cockroachdb/cockroach/pkg/geo/geopb"
)
// Proj4Text is the text representation of a PROJ4 transformation.
type Proj4Text struct {
cStr []byte
}
// MakeProj4Text returns a new Proj4Text with spec based on the given string.
func MakeProj4Text(str string) Proj4Text {
return Proj4Text{
cStr: []byte(str + `\0`),
}
}
// Bytes returns the raw bytes for the given proj text.
func (p *Proj4Text) Bytes() []byte {
return p.cStr
}
// Equal returns whether the two Proj4Texts are equal.
func (p *Proj4Text) Equal(o Proj4Text) bool {
return bytes.Equal(p.cStr, o.cStr)
}
// ProjInfo is a struct containing metadata related to a given SRID.
type ProjInfo struct {
SRID geopb.SRID
// AuthName is the authority who has provided this projection (e.g. ESRI, EPSG).
AuthName string
// AuthSRID is the SRID the given AuthName interprets the SRID as.
AuthSRID int
// SRText is the WKT representation of the projection.
SRText string
// Proj4Text is the PROJ4 text representation of the projection.
Proj4Text Proj4Text
// Denormalized fields.
// IsLatLng stores whether the projection is a LatLng based projection (denormalized from above)
IsLatLng bool
}
// Projection returns the ProjInfo identifier for the given SRID, as well as an bool
// indicating whether the projection exists.
func Projection(srid geopb.SRID) (ProjInfo, bool) {
p, exists := projections[srid]
return p, exists
}
// Copyright 2020 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package geoprojbase
import "github.com/cockroachdb/cockroach/pkg/geo/geopb"
// projections is a mapping of SRID to projections.
// This file is not spell checked.
var projections = map[geopb.SRID]ProjInfo{
4326: {
SRID: 4326,
AuthName: "EPSG",
AuthSRID: 4326,
SRText: `GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]`,
Proj4Text: MakeProj4Text("+proj=longlat +datum=WGS84 +no_defs"),
IsLatLng: true,
},
4004: {
SRID: 4004,
AuthName: "EPSG",
AuthSRID: 4004,
SRText: `GEOGCS["Unknown datum based upon the Bessel 1841 ellipsoid",DATUM["Not_specified_based_on_Bessel_1841_ellipsoid",SPHEROID["Bessel 1841",6377397.155,299.1528128,AUTHORITY["EPSG","7004"]],AUTHORITY["EPSG","6004"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4004"]]`,
Proj4Text: MakeProj4Text("+proj=longlat +ellps=bessel +no_defs"),
IsLatLng: true,
},
3857: {
SRID: 3857,
AuthName: "EPSG",
AuthSRID: 3857,
SRText: `PROJCS["WGS 84 / Pseudo-Mercator",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]],PROJECTION["Mercator_1SP"],PARAMETER["central_meridian",0],PARAMETER["scale_factor",1],PARAMETER["false_easting",0],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["X",EAST],AXIS["Y",NORTH],EXTENSION["PROJ4","+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m [email protected] +wktext +no_defs"],AUTHORITY["EPSG","3857"]]`,
Proj4Text: MakeProj4Text("+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m [email protected] +wktext +no_defs"),
IsLatLng: false,
},
}
// Copyright 2020 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package geotransform
import (
"github.com/cockroachdb/cockroach/pkg/geo"
"github.com/cockroachdb/cockroach/pkg/geo/geopb"
"github.com/cockroachdb/cockroach/pkg/geo/geoproj"
"github.com/cockroachdb/cockroach/pkg/geo/geoprojbase"
"github.com/cockroachdb/errors"
"github.com/twpayne/go-geom"
)
// Transform projects a given Geometry from a given Proj4Text to another Proj4Text.
func Transform(
g *geo.Geometry, from geoprojbase.Proj4Text, to geoprojbase.Proj4Text, newSRID geopb.SRID,
) (*geo.Geometry, error) {
if from.Equal(to) {
return g.CloneWithSRID(newSRID)
}
t, err := g.AsGeomT()
if err != nil {
return nil, err
}
newT, err := transform(t, from, to, newSRID)
if err != nil {
return nil, err
}
return geo.NewGeometryFromGeom(newT)
}
// transform performs the projection operation on a geom.T object.
func transform(
t geom.T, from geoprojbase.Proj4Text, to geoprojbase.Proj4Text, newSRID geopb.SRID,
) (geom.T, error) {
switch t := t.(type) {
case *geom.Point:
newCoords, err := projectFlatCoords(t.FlatCoords(), t.Layout(), from, to)
if err != nil {
return nil, err
}
return geom.NewPointFlat(t.Layout(), newCoords).SetSRID(int(newSRID)), nil
case *geom.LineString:
newCoords, err := projectFlatCoords(t.FlatCoords(), t.Layout(), from, to)
if err != nil {
return nil, err
}
return geom.NewLineStringFlat(t.Layout(), newCoords).SetSRID(int(newSRID)), nil
case *geom.Polygon:
newCoords, err := projectFlatCoords(t.FlatCoords(), t.Layout(), from, to)
if err != nil {
return nil, err
}
return geom.NewPolygonFlat(t.Layout(), newCoords, t.Ends()).SetSRID(int(newSRID)), nil
case *geom.MultiPoint:
newCoords, err := projectFlatCoords(t.FlatCoords(), t.Layout(), from, to)
if err != nil {
return nil, err
}
return geom.NewMultiPointFlat(t.Layout(), newCoords).SetSRID(int(newSRID)), nil
case *geom.MultiLineString:
newCoords, err := projectFlatCoords(t.FlatCoords(), t.Layout(), from, to)
if err != nil {
return nil, err
}
return geom.NewMultiLineStringFlat(t.Layout(), newCoords, t.Ends()).SetSRID(int(newSRID)), nil
case *geom.MultiPolygon:
newCoords, err := projectFlatCoords(t.FlatCoords(), t.Layout(), from, to)
if err != nil {
return nil, err
}
return geom.NewMultiPolygonFlat(t.Layout(), newCoords, t.Endss()).SetSRID(int(newSRID)), nil
case *geom.GeometryCollection:
g := geom.NewGeometryCollection().SetSRID(int(newSRID))
for _, subG := range t.Geoms() {
subGeom, err := transform(subG, from, to, 0)
if err != nil {
return nil, err
}
if err := g.Push(subGeom); err != nil {
return nil, err
}
}
return g, nil
default:
return nil, errors.Newf("unhandled type; %T", t)
}
}
// projectFlatCoords projects a given flatCoords array and returns an array with the projected
// coordinates.
// Note M coordinates are not touched.
func projectFlatCoords(
flatCoords []float64, layout geom.Layout, from geoprojbase.Proj4Text, to geoprojbase.Proj4Text,
) ([]float64, error) {
numCoords := len(flatCoords) / layout.Stride()
// Allocate the map once and partition the arrays to store xCoords, yCoords and zCoords in order.
coords := make([]float64, numCoords*3)
xCoords := coords[numCoords*0 : numCoords*1]
yCoords := coords[numCoords*1 : numCoords*2]
zCoords := coords[numCoords*2 : numCoords*3]
for i := 0; i < numCoords; i++ {
base := i * layout.Stride()
xCoords[i] = flatCoords[base+0]
yCoords[i] = flatCoords[base+1]
// If ZIndex is != -1, it is 2 and forms part of our z coords.
if layout.ZIndex() != -1 {
zCoords[i] = flatCoords[base+layout.ZIndex()]
}
}
if err := geoproj.Project(from, to, xCoords, yCoords, zCoords); err != nil {
return nil, err
}
newCoords := make([]float64, numCoords*layout.Stride())
for i := 0; i < numCoords; i++ {
base := i * layout.Stride()
newCoords[base+0] = xCoords[i]
newCoords[base+1] = yCoords[i]
if layout.ZIndex() != -1 {
newCoords[base+layout.ZIndex()] = zCoords[i]
}
if layout.MIndex() != -1 {
newCoords[base+layout.MIndex()] = flatCoords[i*layout.Stride()+layout.MIndex()]
}
}
return newCoords, nil
}
// Copyright 2020 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.