Mongoose 5.3.8 — невозможно извлечь геоключи с помощью MultiLineString и $push

Я пытаюсь вставить $push пару координат в документ, содержащий GeoJSON MultiLineString, вложенный следующим образом: [[[Number]]]

Когда я нахожу OneAndUpdate так:

req.body.point = [-123,347, 48,43649]

Route.findOneAndUpdate({name: req.body.name},
{'$push': { "route.coordinates.0": req.body.point}}, {new: true})
.then(doc => {
    return res.send(doc)
}).catch(err => {
    res.send(err)
})

Я получаю сообщение об ошибке:

errmsg": "Can't extract geo keys: { _id: ObjectId('5be9eef393311d2d2c6130fd').......
Point must only contain numeric elements",
"code": 16755,

Мои координаты действительны в формате [long, lat], как и ожидает монго. есть что-то, что мне здесь не хватает?


Это моя схема MultiLineString:

const MultiLineStringSchema = new Schema({
  type: {
    type: String,
    enum: ['MultiLineString'],
    required: true,
    default: 'MultiLineString'
  },
  coordinates: {
    type: [[[Number]]],
    required: true
  }
});

Это моя схема маршрута:

var { MultiLineStringSchema } = require('./GeoJson-Schemas')

const RouteSchema = new mongoose.Schema({
  name: String,
  route: {
    type: MultiLineStringSchema,
    required: true
  }
});
RouteSchema.index({ route: "2dsphere" });

РЕДАКТИРОВАТЬ 2: здесь сохранен документ маршрута, в котором сохраняется ошибка. Я воссоздал ошибку с этим документом и обновил соответствующий _id в сообщении об ошибке выше.

{
    "_id": {
        "$oid": "5be9eef393311d2d2c6130fd"
    },
    "name": "A Route",
    "route": {
        "type": "MultiLineString",
        "coordinates": [
            [
                [
                    -123.3867645,
                    48.4813423
                ],
                [
                    -123.3785248,
                    48.4592626
                ],
                [
                    -123.3766365,
                    48.4527165
                ],
                [
                    -123.3756924,
                    48.4523749
                ],
                [
                    -123.3722591,
                    48.4549366
                ],
                [
                    -123.3704567,
                    48.4559612
                ]
            ]
        ],
        "_id": {
            "$oid": "5be9eef393311d2d2c6130fe"
        }
    },
    "__v": 0
}

Продвинувшись на один шаг вперед, я добавил параметр {_id: false} в поддокумент MultiLineString и повторно инициализировал новую коллекцию. При сохранении нового документа следующим образом:

{
    "_id": "5be9f076e1caaa23682d80de",
    "name": "A Route",
    "route": {
        "type": "MultiLineString",
        "coordinates": [
            [
                [
                    -123.3867645,
                    48.4813423
                ],
                [
                    -123.3785248,
                    48.4592626
                ],
                [
                    -123.3766365,
                    48.4527165
                ],
                [
                    -123.3756924,
                    48.4523749
                ],
                [
                    -123.3722591,
                    48.4549366
                ],
                [
                    -123.3704567,
                    48.4559612
                ]
            ]
        ]
    },
    "__v": 0
}

Я пытаюсь найтиOneAndUpdate с точным синтаксисом:

Route.findOneAndUpdate({name},
    {'$push': { "route.coordinates.0": point}}, {new: true})
    .then(doc => {
        return res.send(doc)
    }).catch(err => {
        res.send(err)
    })

И получаю ту же ошибку:

"errmsg": "Can't extract geo keys: { _id: ObjectId('5be9f076e1caaa23682d80de')...
Point must only contain numeric elements",
"code": 16755,

ИЗМЕНИТЬ 3:

Двигаясь вперед еще раз, я обновил RouteSchema, удалив поддокумент полностью, чтобы он выглядел следующим образом:

const RouteSchema = new mongoose.Schema({
  name: String,
  route: {
    type: {
      type: String,
      enum: ['MultiLineString'],
      required: true,
      default: 'MultiLineString'
    },
    coordinates: {
      type: [[[Number]]],
      required: true
    }
  }
});

Я храню документ так:

{
    "_id": {
        "$oid": "5be9f3915fc64e3548603766"
    },
    "route": {
        "type": "MultiLineString",
        "coordinates": [
            [
                [
                    -123.3867645,
                    48.4813423
                ],
                [
                    -123.3785248,
                    48.4592626
                ],
                [
                    -123.3766365,
                    48.4527165
                ],
                [
                    -123.3756924,
                    48.4523749
                ],
                [
                    -123.3722591,
                    48.4549366
                ],
                [
                    -123.3704567,
                    48.4559612
                ]
            ]
        ]
    },
    "name": "A Route",
    "__v": 0
}

и FindOneAndUpdate снова с точным синтаксисом и получают ту же ошибку.

Route.findOneAndUpdate({name},
    {'$push': { "route.coordinates.0": point}}, {new: true})
    .then(doc => {
        return res.send(doc)
    }).catch(err => {
        res.send(err)
    })


"errmsg": "Can't extract geo keys: { _id: ObjectId('5be9f3915fc64e3548603766')
Point must only contain numeric elements",
"code": 16755,

ИЗМЕНИТЬ 4:

mLab размещает экземпляр Mongo версии 3.6.6 индексирует коллекцию Route ФОТО

Запуск соединения оболочки db.routes.getIndexes() ON обеспечивает следующее.

 [
            {
                    "v" : 2,
                    "key" : {
                            "_id" : 1
                    },
                    "name" : "_id_",
                    "ns" : "testbench.routes"
            },
            {
                    "v" : 2,
                    "key" : {
                            "route" : "2dsphere"
                    },
                    "name" : "route_2dsphere",
                    "ns" : "testbench.routes",
                    "background" : true,
                    "2dsphereIndexVersion" : 3
            }
    ]

person Bdyce    schedule 12.11.2018    source источник
comment
Можете ли вы показать фактический документ, соответствующий _id, указанному в ошибке. ObjectId('5be90afce2b53a23e0f83bda'). Сообщение об ошибке предполагает, что данные не хранятся так, как вы ожидаете. Также "Point" в сообщении об ошибке, похоже, также подтверждает это.   -  person Neil Lunn    schedule 12.11.2018
comment
@NeilLunn абсолютно. Я внес некоторые изменения выше, показывающие, как хранится RouteSchema, параметр координат - это вложенный массив, в который я пытаюсь $push войти. спасибо любезно.   -  person Bdyce    schedule 12.11.2018
comment
Хорошо, но это не то редактирование, которое вас просили сделать. Ваше сообщение об ошибке говорит rrmsg": "Can't extract geo keys: { _id: ObjectId('5be90afce2b53a23e0f83bda') Поэтому я прошу вас показать тот документ, идентифицированный данным значением _id. Либо показывая этот документ, либо полное сообщение об ошибке, потому что проблема именно в этом. Почти так же, как и с недопустимой структурой GeoJSON.   -  person Neil Lunn    schedule 12.11.2018
comment
Также обратите внимание, что у вас есть { route: { type, coordinates, _id } }, поскольку вы определили Schema для вложенного документа, в который он автоматически включил _id, что также недопустимо. Вы должны включить { _id: false } в параметры определения схемы или просто встроить определения полей, так как это не то, для чего на самом деле предназначены отдельные определения схемы. Это может быть реальной проблемой здесь, поскольку ошибка может относиться к _id внутри поддокумента route, но предоставление всего документа, фактически соответствующего данной ошибке, проясняет ситуацию. .   -  person Neil Lunn    schedule 12.11.2018
comment
@NeilLunn да, конечно, приношу свои извинения. Первоначально предоставленные данные находились в тестовом стеке и были удалены. Я воссоздал ошибку с точной настройкой и заменил полученную информацию в соответствии с вашими инструкциями, спасибо.   -  person Bdyce    schedule 13.11.2018
comment
Войдите в оболочку и выполните getIndexes() в коллекции. . Ошибка, кажется, указывает на то, что вы сделали что-то действительно неправильное с индексом здесь.   -  person Neil Lunn    schedule 13.11.2018
comment
@NeilLunn моя тестовая база данных — это экземпляр, размещенный в mLab, я добавил фотографию индексов для коллекции маршрутов и результатов db.routes.getIndexes() во время оболочки для экземпляра, спасибо за усилия.   -  person Bdyce    schedule 13.11.2018
comment
Спасибо, извините за задержку, но меня не было. Каково содержание point? На первый взгляд, я мог бы сказать, что вы пытаетесь добавить структуру GeoJSON Point в массив, и это разбивает индекс с сообщением. В основном у вас должно быть что-то, разрешающее { "$push": { "route.coordinates.0": [ -123.3704568, 48.4559612 ] } }, которое, конечно, добавляет новый элемент с индексом без ошибок.   -  person Neil Lunn    schedule 13.11.2018
comment
В качестве примечания вы также можете подумать, что MultiLineString может быть не тем, что вам действительно нужно здесь, и, возможно, было бы лучше хранить Point в отдельных документах. Это зависит от того, что вы действительно собираетесь делать с данными, и вопрос не в описании этого намерения. В 9/10 случаях люди просто извлекают GeoJSON из внешнего источника и сохраняют его именно в том виде, в котором они его получили, и в большинстве этих случаев вам действительно лучше хранить отдельные Point документы в коллекции с некоторой связанной информацией для группы.   -  person Neil Lunn    schedule 13.11.2018
comment
Возвращаясь к теме, point здесь более чем правдоподобная проблема. Поскольку вы используете mongoose, он творит чудеса, сравнивая оператор update() (и варианты) со схемой и выполняя приведение типов. Таким образом, даже если это выглядит как массив, такие вещи, как строки для элементов, могут быть неправильно приведены к типу. Вероятно, это артефакт нотации coordinates.0, которая строго не сочетается со структурой вложенного массива, что приводит к тому, что преобразование Number не происходит. Кастинг вручную через parseFloat должен исправить это.   -  person Neil Lunn    schedule 13.11.2018
comment
@NeilLunn Эй, да без проблем, я все равно на работе. код findOneAndUpdate в mongoose преобразует point в [-123.3701134, 48.4467389], где координаты относятся к длинному/широтному числу с плавающей запятой и являются точкой на сфере. Фактическая переменная point в коде обновления разрешается в массив. Идея, лежащая в основе схемы MultiLineString поверх чего-то вроде схемы Point или LineString, состоит в том, чтобы иметь возможность протолкнуть новую часть маршрута в первый массив, что приведет к двум LineStrings, чтобы маршрут мог закончиться и продолжиться где-то еще.   -  person Bdyce    schedule 13.11.2018
comment
@NeilLunn Я попытался вручную проанализировать числа, поступающие из req.body.point, назначив массив переменной и используя parseFloat(coords[0]) и parseFloat(coords[1]), а затем вручную загрузив значения в массив, который будет {'$push': { "route.coordinates.0": [long, lat]}} помещен в массив, и все равно не повезло, то же самое ошибка сохраняется.   -  person Bdyce    schedule 13.11.2018
comment
В основном вы можете попытаться свести проблему к минимальному, полному и проверяемому примеру, представляющему собой автономный список кода, который фактически воспроизводит проблему. . Для меня, просто пытаясь обновить значения, которые вы говорите, что у вас есть, и документ, который вы говорите, не воспроизводит ошибку. MCVE может, по крайней мере, указать на потенциальные проблемы, если не полностью решить их в процессе построения. В нынешнем виде я не могу воспроизвести это.   -  person Neil Lunn    schedule 13.11.2018
comment
@NeilLunn Я переписал код в изолированной среде, используя только две схемы, новую базу данных и индексы, а также два экспресс-маршрута со всеми примерами, о которых мы говорили. Ошибка по-прежнему сохраняется. Если у вас есть рабочий пример, я хотел бы взглянуть на него, я не могу понять проблему или найти решение. спасибо за прохождение я ценю это.   -  person Bdyce    schedule 13.11.2018
comment
Все в порядке, я был заинтригован и все равно воспроизвел проблему и решение самостоятельно.   -  person Neil Lunn    schedule 13.11.2018


Ответы (1)


Таким образом, проблема действительно воспроизводима и сводится к определению схемы. Прямо здесь:

coordinates: {
  type: [[[Number]]],
  required: true
}

Здесь вы старательно указываете «кольца» вложенного массива, необходимые для формата MultiLineString документа. Это может показаться правильным, и вставка новых документов не является проблемой, но обратите внимание на обозначения, которые MongoDB хочет здесь:

{ $push: { 'route.coordinates.0': [-123.3701134, 48.4467389] } }

Таким образом, «схема» имеет «гнездо двойного массива», как в [[[Number]]], но MongoDB хочет только coordinates.0, а не coordinates.0.0, чтобы быть счастливым. Вот где мангуст запутался и "переписывает" оператор обновления:

{ '$push': { 'route.coordinates.0': [ [ -123.3701134 ], [ 48.4467389 ] ] } }

Так что это просто неправильно и является источником ошибки:

errmsg: 'Не удается извлечь геоключи: { _id: ObjectId(\'5be9f3915fc64e3548603766\'), маршрут: { тип: "MultiLineString", координаты: [ [ [ -123.3867645, 48.4813423], [-123.3785248, 48.4592626], [-123.3766365, 48.4527165], [-123.3756924, 48.4523749, 48.4523749], [-123.3722591, 48.4549366], [-123.3704567, 48.4559612], [-123.3701134], [48.4467389]]]]}, Имя: «Маршрут», __v : 0 } Точка должна содержать только числовые элементы',

Изменение схемы, чтобы она не была «настолько конкретной», останавливает мангуста от «искажения» оператора:

coordinates: {
  type: []   // Just expect an array without any other definition
  required: true
}

Затем обновление проходит, как и ожидалось.


Для полного воспроизводимого списка:

const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');

// Sample connection
const uri = 'mongodb://localhost:27017/geostring';
const opts = { useNewUrlParser: true };

// Sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);
mongoose.set('debug', true);

// Schema defs


const routeSchema = new Schema({
  name: String,
  route: {
    type: {
      type: String,
      enum: ['MultiLineString'],
      required: true,
      default: 'MultiLineString'
    },
    coordinates: {
      type: [],
      // type: [[[Number]]], // this has a problem
      required: true
    }
  }
});

routeSchema.index({ route: '2dsphere' });

const Route = mongoose.model('Route', routeSchema);

// log helper
const log = data => console.log(JSON.stringify(data, undefined, 2));

// Main
(async function() {

  try {
    const conn = await mongoose.connect(uri, opts);

    // clean data from all models
    await Promise.all(
      Object.entries(conn.models).map(([k,m]) => m.deleteMany())
    );

    // Insert data

    await Route.create({
      "_id": new ObjectId("5be9f3915fc64e3548603766"),
      "name": "A Route",
      "route": {
        "type": "MultiLineString",
        "coordinates": [
          [
            [
              -123.3867645,
              48.4813423
            ],
            [
              -123.3785248,
              48.4592626
            ],
            [
              -123.3766365,
              48.4527165
            ],
            [
              -123.3756924,
              48.4523749
            ],
            [
              -123.3722591,
              48.4549366
            ],
            [
              -123.3704567,
              48.4559612
            ]
          ]
        ]
      }
    });

    // Test operation

    let result = await Route.findOneAndUpdate(
      { name: 'A Route' },
      { $push: { 'route.coordinates.0': [-123.3701134, 48.4467389] } },
      { new: true }
    );
    log(result);

  } catch(e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }

})()

И «правильный» вывод из исправленной схемы:

Mongoose: routes.createIndex({ route: '2dsphere' }, { background: true })
Mongoose: routes.deleteMany({}, {})
Mongoose: routes.insertOne({ route: { type: 'MultiLineString', coordinates: [ [ [ -123.3867645, 48.4813423 ], [ -123.3785248, 48.4592626 ], [ -123.3766365, 48.4527165 ], [ -123.3756924, 48.4523749 ], [ -123.3722591, 48.4549366 ], [ -123.3704567, 48.4559612 ] ] ] }, _id: ObjectId("5be9f3915fc64e3548603766"), name: 'A Route', __v: 0 })
Mongoose: routes.findOneAndUpdate({ name: 'A Route' }, { '$push': { 'route.coordinates.0': [ -123.3701134, 48.4467389 ] } }, { upsert: false, remove: false, projection: {}, returnOriginal: false })
{
  "route": {
    "type": "MultiLineString",
    "coordinates": [
      [
        [
          -123.3867645,
          48.4813423
        ],
        [
          -123.3785248,
          48.4592626
        ],
        [
          -123.3766365,
          48.4527165
        ],
        [
          -123.3756924,
          48.4523749
        ],
        [
          -123.3722591,
          48.4549366
        ],
        [
          -123.3704567,
          48.4559612
        ],
        [
          -123.3701134,
          48.4467389
        ]
      ]
    ]
  },
  "_id": "5be9f3915fc64e3548603766",
  "name": "A Route",
  "__v": 0
}
person Neil Lunn    schedule 13.11.2018
comment
Великолепно, спасибо за понимание, Нил, это прекрасно работает. Ценю всю вашу помощь сегодня. - person Bdyce; 13.11.2018