Работа с пространственными данными с помощью Gorm и MySQL - PullRequest
4 голосов
/ 04 марта 2020

Я сослался на ответ irbanana о поддержке типа пространственных данных для PostGIS. Я использую MySQL и пытаюсь реализовать Value() для пользовательского типа данных EWKBGeomPoint.

Моя модель Gorm:

import (
    "github.com/twpayne/go-geom"
    "github.com/twpayne/go-geom/encoding/ewkb"
)

type EWKBGeomPoint geom.Point

type Tag struct {
    Name string `json:"name"`
json:"siteID"` // forign key
    Loc EWKBGeomPoint `json:"loc"`
}

Из того, что я знаю, MySQL поддерживает вставку следующим образом:

INSERT INTO `tag` (`name`,`loc`) VALUES ('tag name',ST_GeomFromText('POINT(10.000000 20.000000)'))

или

INSERT INTO `tag` (`name`,`loc`) VALUES ('tag name', ST_GeomFromWKB(X'0101000000000000000000F03F000000000000F03F'))

Если я сделаю свой собственный Value(), чтобы удовлетворить database/sql 'Valuer интерфейс:

func (g EWKBGeomPoint) Value() (driver.Value, error) {
    log.Println("EWKBGeomPoint value called")
    b := geom.Point(g)
    bp := &b

    floatArr := bp.Coords()
    return fmt.Sprintf("ST_GeomFromText('POINT(%f %f)')", floatArr[0], floatArr[1]), nil
}

Все значение, включая ST_GeomFromText(), указано в одинарной кавычке от Gorm, и поэтому оно не будет работать:

INSERT INTO `tag` (`name`,`loc`) VALUES ('tag name','ST_GeomFromText('POINT(10.000000 20.000000)')');

Как мне заставить это работать?

РЕДАКТИРОВАТЬ 1:

Я прослеживаю код Горм, в конце концов он получает функцию callback_create.go createCallback. Внутри него проверьте на if primaryField == nil, и это правда, он вызывает scope.SQLDB().Exec, тогда я не смог отследить дальше.

scope. SQL является строкой INSERT INTO tag ( name , lo c) VALUES (?,?) и scope.SQLVars print [tag name {{1 2 [10 20] 0}}]. Похоже, что внутри этого вызова происходит интерполяция.

Это вызов в database/sql код?

РЕДАКТИРОВАТЬ 2:

Найден похожий вопрос Stackoverflow здесь . Но я не понимаю решение.

Ответы [ 2 ]

1 голос
/ 07 марта 2020

Вот другой подход; используйте двоичное кодирование.

В соответствии с этим do c, MySQL хранит значения геометрии, используя 4 байта, чтобы указать SRID (идентификатор пространственной привязки), за которым следует WKB (хорошо известный двоичный файл) ) представление значения.

Таким образом, тип может использовать кодирование WKB, добавлять и удалять четырехбайтовый префикс в функциях Value () и Scan (). Библиотека go -geom, найденная в других ответах, имеет пакет кодировки WKB, github.com/twpayne/go-geom/encoding/wkb.

Например:

type MyPoint struct {
    Point wkb.Point
}

func (m *MyPoint) Value() (driver.Value, error) {
    value, err := m.Point.Value()
    if err != nil {
        return nil, err
    }

    buf, ok := value.([]byte)
    if !ok {
        return nil, fmt.Errorf("did not convert value: expected []byte, but was %T", value)
    }

    mysqlEncoding := make([]byte, 4)
    binary.LittleEndian.PutUint32(mysqlEncoding, 4326)
    mysqlEncoding = append(mysqlEncoding, buf...)

    return mysqlEncoding, err
}

func (m *MyPoint) Scan(src interface{}) error {
    if src == nil {
        return nil
    }

    mysqlEncoding, ok := src.([]byte)
    if !ok {
        return fmt.Errorf("did not scan: expected []byte but was %T", src)
    }

    var srid uint32 = binary.LittleEndian.Uint32(mysqlEncoding[0:4])

    err := m.Point.Scan(mysqlEncoding[4:])

    m.Point.SetSRID(int(srid))

    return err
}

Определение тега с использованием типа MyPoint:

type Tag struct {
    Name string   `gorm:"type:varchar(50);primary_key"`
    Loc  *MyPoint `gorm:"column:loc"`
}

func (t Tag) String() string {
    return fmt.Sprintf("%s @ Point(%f, %f)", t.Name, t.Loc.Point.Coords().X(), t.Loc.Point.Coords().Y())
}

Создание тега с использованием типа:

tag := &Tag{
    Name: "London",
    Loc: &MyPoint{
        wkb.Point{
            geom.NewPoint(geom.XY).MustSetCoords([]float64{0.1275, 51.50722}).SetSRID(4326),
        },
    },
}

err = db.Create(&tag).Error
if err != nil {
    log.Fatalf("create: %v", err)
}

MySQL результаты:

mysql> describe tag;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| name  | varchar(50) | NO   | PRI | NULL    |       |
| loc   | geometry    | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+


mysql> select name, st_astext(loc) from tag;
+--------+------------------------+
| name   | st_astext(loc)         |
+--------+------------------------+
| London | POINT(0.1275 51.50722) |
+--------+------------------------+
  • ( ArcGIS говорит, что 4326 - это наиболее распространенная пространственная привязка для хранения ссылочных данных во всем мире. Она используется по умолчанию для как пространственная база данных PostGIS, так и стандарт Geo JSON. Он также используется по умолчанию в большинстве библиотек веб-картографии.)
0 голосов
/ 05 марта 2020

Обновление: этот подход не сработал.

Хуки могут позволить вам установить для столбца значение gorm.Expr до Гормса sql поколение.

Например, что-то вроде этого перед вставкой:

func (t *Tag) BeforeCreate(scope *gorm.Scope) error {

  x, y := .... // tag.Loc coordinates

  text := fmt.Sprintf("POINT(%f %f)", x, y)

  expr := gorm.Expr("ST_GeomFromText(?)", text)

  scope.SetColumn("loc", expr)

  return nil
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...