7 мин

Мобильное приложение для геодезистов: офлайн точки и фото

Мобильное приложение для геодезистов: модель данных объект-точка-измерение, офлайн-работа с фото и синхронизация с сервером через AI-кодинг.

Мобильное приложение для геодезистов: офлайн точки и фото

Что именно нужно геодезисту в полевом приложении

В поле важен не «красивый интерфейс», а быстрый и надежный способ фиксировать результаты без потерь. Приложением будут пользоваться и бригадир, и исполнитель на точке: первому нужен прогресс по объекту, второму - быстрый ввод измерения, фото и заметки, чтобы сразу идти дальше.

Набор данных обычно повторяется от проекта к проекту. На уровне объекта (участок, стройплощадка, трасса) нужны общие сведения: название, заказчик, даты, состав бригады, заметки. Внутри объекта создаются точки: номер/код, координаты (если уже известны), тип (ось, репер, пикет), статус (запланирована, снята, проверена) и привязка к месту (карта или список).

На каждой точке фиксируются измерения: что измеряли (высота, расстояние, отметка), значение, единицы, метод, прибор, время и исполнитель. Комментарии должны жить рядом с точкой или конкретным измерением: «ветер сильный», «штатив просел», «повторить после обеда». Фото часто выручает, когда нужно объяснить, где стояла точка, что мешало обзору или почему приняли решение на месте.

Офлайн-режим - обязательный. Связь в поле пропадает, батарея уходит быстрее на холоде, экран мокрый от дождя, руки в перчатках. Приложение должно полностью работать без интернета: создавать объект, добавлять точки, сохранять фото, быстро открывать списки и не зависать.

Для первого релиза обычно достаточно четырех основных экранов:

  • список объектов с быстрым поиском и статусами
  • карта (или схема) с точками и фильтрами
  • карточка точки: координаты, фото, комментарии, статус
  • журнал измерений: добавление, правка, история изменений

Если начать с этих экранов и четкой структуры данных, позже проще «нарастить» офлайн-хранилище и синхронизацию.

Модель данных: объект -> точка -> измерение

Чтобы приложение работало без сюрпризов, начните с простой и устойчивой модели: объект (проект) содержит точки, а у каждой точки есть набор измерений. Это разделяет три разных смысла: что снимаем, где это на местности и как именно получили значения.

Объект - контейнер для всей съемки. В нем хранится то, что общее для всех точек: название, заказчик, выбранная система координат, статус работ (черновик, в работе, сдано), ответственный.

Точка - конкретное место на объекте. У нее есть координаты (X/Y или широта/долгота), высота, тип (пикет, репер, угол здания), оценка точности и метки (например, «нужна проверка»). Фото и комментарии чаще привязывают к точке: так легче восстановить контекст.

Измерение - запись о том, как получены данные по точке. Здесь лежат метод (GNSS, тахеометрия, нивелирование), прибор, дата и время, измеренные значения, погрешность и служебные параметры (например, режим съемки).

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

  • Объект: id, название, система координат, статус
  • Точка: id, object_id, координаты, тип, время создания
  • Измерение: id, point_id, метод, дата/время, значения

Остальные поля можно сделать опциональными: заказчик, высота, точность, метки, прибор, погрешность, фото, комментарии. На рекогносцировке, например, удобно быстро набросать точки с фото и короткими заметками, а точные измерения добавить позже.

Связи, ключи и правила целостности

Чтобы приложение не превратилось в набор несвязанных заметок, связи и ключи стоит продумать до экранов и синхронизации. База простая: объект содержит точки, точка содержит измерения. Офлайн добавляет детали, которые лучше заложить сразу.

Связи 1:N и ключи

Связи обычно такие: Object 1:N Point, Point 1:N Measurement. На уровне хранения удобно явно фиксировать внешние ключи: у точки есть objectLocalId, у измерения - pointLocalId. Это помогает фильтровать данные быстро и не терять контекст, даже если сервер еще не выдал свои идентификаторы.

Для каждой сущности полезно иметь два идентификатора:

  • localId (UUID) - создается на телефоне сразу, используется офлайн и как ключ связей
  • serverId - появляется после синхронизации, нужен для API и сверки

Дополнительно часто нужны:

  • clientId или deviceId - чтобы понимать источник изменений при конфликтах
  • syncState (например, new, dirty, synced, deleted) - чтобы вести очередь отправки

Практическое правило: пока serverId пустой, все связи строятся только через localId. Иначе легко получить «сиротские» измерения или фото.

Признаки изменения и мягкое удаление

Для надежной синхронизации добавьте метки изменений: updatedAt и deletedAt (мягкое удаление), плюс version или revision (целое число). Тогда можно уверенно отвечать на вопросы: что изменилось, что удалено, чья версия новее.

Правила целостности лучше сделать явными и в UI, и в данных. Например, удаление точки с измерениями:

  • по умолчанию запрещено и требует подтверждения
  • при подтверждении удаляются (или помечаются deletedAt) все измерения точки

Так на сервер не улетят измерения без точки.

Вложения: фото как отдельная сущность

Фото и файлы удобнее хранить отдельной сущностью Attachment с привязкой к точке (и при необходимости к измерению). В таблице держите метаданные: pointLocalId, тип, комментарий, localPath, sha256, createdAt, uploadedAt.

Сценарий простой: в поле фото сохраняется локально и попадает в очередь, а при появлении связи отправляется на сервер вместе с метаданными.

Если вы собираете это через TakProsto, зафиксируйте в планировании правило «все связи только через localId до получения serverId» и попросите сгенерировать миграции и проверки на удаление. Это обычно экономит часы отладки офлайн-ошибок.

Пользовательские сценарии и экраны (без лишнего)

В первой версии важнее всего быстрый ввод в поле и понятная проверка данных. Когда экранов слишком много, люди начинают писать «на бумажку» и переносить позже, а это убивает смысл офлайна.

Минимальный набор экранов можно держать коротким:

  • Объекты: список объектов и кнопка «Новый объект»
  • Объект (карточка): описание, статус, счетчики точек и фото
  • Точки: список точек внутри объекта + поиск по коду/номеру
  • Точка (карточка): координаты, заметка, фото, список измерений
  • Измерение (форма): тип измерения, значение, единицы, время, исполнитель

Рабочий поток должен идти «по рукам» без лишних шагов: создали объект, добавили точки (часто сразу с GPS), в каждой точке внесли измерения и прикрепили фото с коротким комментарием.

В поле решают быстрые действия. Помогают две вещи: заметная кнопка «Добавить точку» в объекте и шаблоны измерений (например, «отметка», «угол», «расстояние»), чтобы не выбирать тип каждый раз.

Чтобы не ловить ошибки при синхронизации, валидации лучше делать на вводе: обязательные поля (код точки, тип измерения, единицы), формат чисел, переключатель единиц (метры/миллиметры), разумные диапазоны. Для «подозрительных» значений лучше показывать предупреждение, а не блокировку.

Карта нужна, но простая: маркер точки + рядом список точек с расстоянием и временем обновления. Достаточно двух действий: нажал на маркер - открыл карточку точки; выбрал точку в списке - подсветил на карте.

Офлайн-хранилище: как хранить точки, измерения и фото

Главное правило полевого софта простое: любое действие сначала записывается локально, а синхронизация - отдельный процесс, который догоняет позже.

Для структурированных данных удобен SQLite: быстрый, встроенный, хорошо ложится на «объект -> точка -> измерение». Важнее не выбор БД, а подготовка запросов под полевой поиск. Если бригадир ищет точку по коду или фильтрует по статусу «не снято», приложение не должно тормозить. Поэтому заранее добавьте индексы: по object_id, point_code, updated_at, deleted_at (если есть), а также по sync_state.

Фото лучше хранить не в базе, а файлами на устройстве. В SQLite оставьте метаданные: путь, размер, время съемки, привязку к точке или измерению и контрольную сумму (хотя бы простую) для поиска дублей. Так база остается легкой, а фото можно загружать по одному, не блокируя работу.

Чтобы офлайн не превращался в хаос, заведите очередь изменений. На практике это либо журнал операций (append-only), либо таблица pending_changes. Минимальный набор полей:

  • entity_type и entity_id (что меняли)
  • op (insert/update/delete)
  • payload (что поменялось)
  • created_at и retry_count
  • status (pending/sent/failed)

Удаление делайте мягким: вместо реального delete ставьте deleted_at и deleted_by. Физическую уборку фото и строк в БД выполняйте только после подтверждения от сервера.

В интерфейсе показывайте понятный статус: «все сохранено локально», «ждет отправки», «ошибка синхронизации». Пользовательские действия (создание точки, измерения, фото, комментарии) должны идти только через локальное хранилище, а фоновый синк лишь меняет статусы.

Если делаете через TakProsto, заранее попросите сгенерировать миграции SQLite, модели и слой репозитория, чтобы не забыть индексы и поля sync_state.

Синхронизация: протокол, конфликты и надежность

Проверьте поле без интернета
Прогоните сценарий 30 точек и фото в авиарежиме и проверьте статусы отправки.

Синхронизация в полевом софте должна быть скучной и предсказуемой. Пользователь работает офлайн, а при появлении сети данные уходят на сервер без потерь. Для такого сценария хорошо подходит схема pull + push с версиями.

Минимальный протокол синхронизации

На сервере держите простые операции и версионирование (revision) для каждой сущности (объект, точка, измерение, вложения):

  • Pull: получить изменения после lastSyncToken (или по времени + курсор)
  • Push: принять пачку локальных операций (create/update/delete) с revision, clientId и временем
  • Ack: вернуть принятые операции, новые revision и новый syncToken
  • Resolve: вернуть конфликты по полям, если не удалось применить
  • Bootstrap: первичная загрузка объекта целиком

Pull обычно делайте первым: так клиент увидит свежие правки коллег и только потом отправит свои.

Конфликты: кто победит

Самая частая ошибка - «последняя запись победила» для всей сущности. Практичнее считать конфликтом конкретные поля.

Пример: один инженер сменил тип точки, другой добавил фото и комментарий. Это не конфликт. Поэтому текстовые поля и атрибуты точки лучше решать через выбор (мое/серверное), а измерения и фото чаще всего просто «сливать» как отдельные записи.

Фото и надежность

Фото синхронизируйте в два шага: сначала метаданные, потом файл. Файл грузите с повторами и возобновлением, чтобы не начинать заново после обрыва.

Чтобы синк не ломался при слабой сети, добавьте:

  • идемпотентность (один и тот же push можно повторить без дублей)
  • ретраи с растущей паузой и лимитом
  • небольшие пачки операций и таймауты
  • фоновую очередь отправки, которая не мешает работе

Такую логику удобно быстро собрать в TakProsto: описываете правила синка и конфликты словами, а затем проверяете на симуляции «сеть пропала на 30% запросов».

Пошагово: реализуем через AI-кодинг от модели до синка

Задача прототипа простая: создать объект, добавить точки, прикрепить фото, работать без связи и безопасно отправить изменения на сервер.

В TakProsto удобно стартовать с Planning mode: сначала описываете данные и экраны словами, затем просите платформу сгенерировать каркас проекта.

Шаги, которые дают результат за 1-2 итерации

  1. Опишите требования простыми правилами: какие поля нужны у объекта, точки и измерения, что обязательно, как пользователь поймет, что данные сохранены офлайн.
  2. Попросите сделать модель и схемы: таблицы/коллекции для Object, Point, Measurement, Photo, плюс статусы синхронизации и даты изменений.
  3. Сгенерируйте офлайн-слой на SQLite в мобильном приложении (Flutter): репозитории, CRUD, очередь изменений и хранение фото как файлов с привязкой к записи.
  4. Сгенерируйте бэкенд API (Go + PostgreSQL): эндпоинты для выгрузки изменений пачками, приема новых версий, загрузки фото.
  5. Добавьте статусы синка в UI: «Не отправлено», «В очереди», «Отправлено», «Ошибка». Ошибки показывайте коротко, с действием «Повторить».

Пример задачи для AI: «Нужно хранить измерение с типом (угол, расстояние), значением, единицей, временем, автором. Для синка используем serverVersion и clientUpdatedAt. При конфликте побеждает запись с более новым clientUpdatedAt, но сохраняем старую в history».

Что проверить сразу после генерации

  • миграции: создаются ли таблицы и обновляются ли без потери данных
  • индексы: по objectId, pointId, updatedAt и статусу синка
  • обработка ошибок сети: таймауты, повторы, работа в режиме самолета
  • идентификаторы: UUID на клиенте, маппинг на сервер, отсутствие дублей
  • фото: путь к файлу, очистка, повторная отправка при сбое

После этого сделайте простой тест: создайте объект, 10 точек и по 2 фото офлайн, затем включите сеть и проверьте, что все записи перешли в статус «Отправлено» без пропусков.

Бэкенд и хранение данных: минимальная архитектура

Данные остаются в России
Соберите решение на серверах в России с локализованными моделями без отправки данных за рубеж.

Для полевого приложения важнее всего надежно принять данные, привязать их к объекту и пользователю, сохранить версии и отдать их обратно при синхронизации. Технологически логичный минимум: Flutter на мобильной стороне, а на сервере Go + PostgreSQL.

Если вы собираете прототип через TakProsto, этот стек используется часто: мобильная часть на Flutter, сервер на Go, база PostgreSQL. Это упрощает старт и поддержку.

Минимальные таблицы для синхронизации

На сервере обычно хватает понятной структуры и метаданных синка:

  • users (или users + teams/brigades)
  • objects
  • points
  • measurements
  • attachments

В каждой сущности держите: server_id, client_uuid, updated_at, deleted_at (для мягкого удаления) и автора изменения. Это помогает синхронизировать офлайн-редактирование и не терять историю.

Аутентификация и доступ

На старте достаточно простого токена: пользователь логинится, получает access token, а сервер проверяет его в каждом запросе. Данные удобно привязывать либо к пользователю, либо к бригаде, если несколько людей работают по одному объекту.

Фото и файлы

Фото лучше хранить файлами, а в PostgreSQL держать только метаданные и связь с точкой/измерением. На практике часто используют S3-совместимый объектный сторедж, развернутый в РФ, плюс таблицу attachments с путями, хэшами и статусом загрузки.

Если проект должен гарантированно работать в России, размещайте сервер, базу и файловое хранилище в пределах РФ и фиксируйте это в настройках и договорах. Тогда данные не «уедут» в другие страны и проще пройти требования заказчиков.

Частые ошибки в офлайн и синхронизации

Первая боль почти всегда начинается с идентификаторов. Когда в интерфейсе смешивают локальные id и серверные id, связи «объект -> точка -> измерение» рвутся: точка есть, а измерения не находятся, или фото «переезжает» на другую точку после синка. Надежнее сразу заводить стабильный локальный UUID и хранить отдельно server_id, а в UI и связях использовать локальный ключ.

Вторая ошибка - нет журнала изменений. Если приложение не знает, что именно пользователь сделал офлайн (создал точку, изменил комментарий, удалил фото), синхронизация превращается в «отправим все подряд» или в гадание. Нужен простой outbox: очередь операций с типом, временем, ссылкой на локальные сущности и статусом отправки.

Фото часто кладут прямо в базу одним большим blob. Это быстро упирается в размер, скорость и сбои при записи. Лучше хранить метаданные в БД (путь, хэш, размер, привязка к точке), а сами файлы - в файловом кеше приложения с понятной стратегией: сжатие, превью, повторная отправка.

Если нет стратегии конфликтов, данные тихо перетираются. Пример: инженер в поле правит комментарий к точке, а диспетчер в офисе меняет тип измерения. При синке «последний записавший победил» может уничтожить важную правку. Минимум - версия/updated_at и явное правило по полям, максимум - показ конфликта пользователю.

И еще один типичный провал: синхронизация «по одной записи» медленная и ест батарею. Практичнее паковать изменения батчами и отправлять по событиям (появилась связь, пользователь нажал «Синхронизировать», приложение на зарядке).

Проверьте перед запуском:

  • UUID локально, server_id отдельно, маппинг хранится явно
  • outbox с операциями и повторами
  • фото: файлы отдельно, в БД только метаданные
  • конфликты: версия и понятные правила
  • батчи и лимиты, чтобы не разряжать телефон

В TakProsto удобно сначала описать эти правила в Planning mode, а затем сгенерировать офлайн-слой и синк так, чтобы они им следовали.

Быстрый чеклист перед выездом в поле

Перед выездом на объект прогоните короткий тест на одной «учебной» съемке. Это занимает 10-15 минут, но часто спасает день.

Минимальный офлайн-тест (без интернета)

Переведите телефон в авиарежим и работайте так, будто связи не будет:

  • создайте объект и откройте его заново (без «вечной загрузки»)
  • добавьте 5-10 точек и по 1-2 измерения на каждую, проверьте поиск и фильтры
  • сделайте 2-3 фото к разным точкам, добавьте комментарии, перезапустите приложение и проверьте привязку
  • убедитесь, что новые данные имеют статус (например, «не отправлено») и он не теряется после перезапуска

Затем выберите одну точку и специально внесите правку: измените комментарий и одно значение измерения. Так проще поймать баг, когда редактирование создает «вторую версию» вместо обновления.

Проверка синхронизации и конфликтов

Верните интернет и дождитесь отправки:

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

Если вы делаете прототип через TakProsto, держите этот чеклист рядом: он быстро показывает, где ломается офлайн-хранилище, вложения (фото) или логика синхронизации.

Пример из практики: съемка участка без связи

Прототип для геодезистов за вечер
Соберите прототип полевого приложения через чат и быстро проверьте офлайн сценарии.

Бригада выезжает на трассу: связи нет почти весь день, план жесткий - снять около 30 точек до вечера. В приложении важно, чтобы работа не зависела от интернета: все создается, сохраняется и ищется локально, а синхронизация ждет, пока появится сеть.

Объект заводят на месте: название (например, «Км 12-18, обочина»), заказчик, дата, бригада, прибор. Дальше выбирают шаблон точек: «опоры», «границы», «колодцы», «пикеты». Шаблон подставляет тип точки, обязательные поля и стандартный набор измерений, чтобы не тратить время на ручной ввод.

На каждой точке чаще всего заполняют минимум: номер/код, тип, координаты (GNSS или из тахеометра), высоту, точность/качество фиксации, статус (черновик или готово). Важно, чтобы приложение позволяло оставить часть данных «на потом»: описание, принадлежность к участку, дополнительные параметры, уточняющие заметки.

Фото и комментарии добавляют на месте за 10-15 секунд. Обычно хватает 1-3 фото: общий вид, привязка к ориентиру, табличка или маркировка. Комментарий короткий и прикладной: «мешает куст, смещение 0.3 м», «точка у столба, отметка на бетоне», «доступ через калитку». Фото сохраняются локально, а в точке лежит ссылка на файл и флаг «не загружено».

Вечером, когда появляется интернет, пользователь нажимает «Синхронизировать». Приложение отправляет новые объекты, точки, измерения, затем фото (обычно это самый долгий этап) и получает подтверждение. После этого можно собрать простой отчет: сколько точек готово, где есть пропуски, какие фото не прикреплены, что осталось в черновиках. Хорошо, если синхронизация умеет продолжать с места остановки, если сеть снова пропала.

Следующие шаги: прототип и развитие проекта

Начните не с «всего сразу», а с одного главного сценария: создать объект, добавить точку, записать одно измерение, прикрепить фото и комментарий, затем синхронизировать при появлении связи.

Хороший стартовый план:

  • описать поля для «объект -> точка -> измерение» и правила: что обязательно, что может быть пустым
  • сделать экран списка объектов и экран точки с фото, комментариями и измерениями
  • реализовать офлайн: локальная база, очередь изменений, статусы «не отправлено/отправлено/ошибка»
  • добавить синхронизацию «в одну кнопку» и понятные сообщения при конфликте
  • проверить на реальном сценарии: 30-50 точек, много фото, полетный режим

Дальше расширяйте итеративно, только когда базовый сценарий не ломается. Обычно первыми добавляют новые типы измерений (разные приборы и единицы), экспорт (CSV/GeoJSON по требованиям заказчика), роли (исполнитель, проверяющий), историю изменений и журнал действий. Модель лучше усложнять постепенно: чаще достаточно справочника типов измерений, чем множества новых сущностей.

Для быстрого старта TakProsto позволяет через чат собрать прототип экранов и модели, а затем точечно доработать офлайн и синк. Если вы работаете на takprosto.ai, полезно сразу включить то, что помогает не бояться правок: снапшоты и откат, экспорт исходников, деплой и хостинг для тестового сервера.

По тарифам логика обычно простая: начать с free для проверки идеи и UX, перейти на pro, когда нужно больше проектов и стабильные сборки, и рассмотреть business, если работает команда и важны управляемые доступы и процессы.

FAQ

Какая минимальная модель данных нужна для полевого геодезического приложения?

Начните с тройки сущностей: Объект хранит общие данные съемки, Точка описывает место и контекст, Измерение фиксирует, как именно получено значение. Такая структура не путает «где» и «как измерили» и хорошо работает для офлайна и синхронизации.

Какие поля лучше сделать обязательными, а какие оставить необязательными?

Сделайте обязательными только поля, без которых дальше все ломается: у объекта — id, название, система координат, статус; у точки — id, ссылка на объект, код/номер, координаты или возможность их оставить пустыми, тип и время создания; у измерения — id, ссылка на точку, метод, дата/время и значение. Остальное оставьте опциональным, чтобы в поле можно было быстро набросать данные и дополнять позже.

Как правильно работать с localId и serverId, чтобы не рвались связи?

Используйте два идентификатора: localId (UUID) создается на телефоне сразу и участвует во всех связях, а serverId появляется после синхронизации. Пока serverId пустой, связывайте точки, измерения и фото только через localId, иначе легко получить «сиротские» записи и проблемы при повторной отправке.

Как организовать офлайн-хранилище, чтобы приложение не зависело от интернета?

Правило простое: любое действие сначала пишется локально, а синхронизация идет отдельным фоновым процессом. Для структуры «объект → точка → измерение» обычно хватает SQLite и правильно подобранных индексов, чтобы поиск по коду точки и фильтры по статусам не тормозили даже на большом объекте.

Зачем нужна очередь изменений (outbox) и что в нее писать?

Держите очередь изменений (outbox): приложение складывает туда операции insert/update/delete с временем, типом сущности и статусом отправки. Тогда синк не «угадывает», что делать, а просто последовательно отправляет накопленные операции, умеет повторять при ошибках и не теряет изменения после перезапуска.

Как лучше хранить и синхронизировать фото, чтобы ничего не пропадало?

Фото храните файлами на устройстве, а в базе держите только метаданные: привязку к точке или измерению, локальный путь, время создания и признак загрузки. При синхронизации сначала отправляйте метаданные, а потом файл, чтобы можно было безопасно возобновлять загрузку после обрыва связи.

Как безопасно удалять точки и измерения в офлайне?

Используйте мягкое удаление через deletedAt, а не физический delete, и удаляйте окончательно только после подтверждения от сервера. Если пользователь удаляет точку, заранее определите правило для измерений и вложений: чаще всего их тоже нужно помечать удаленными, чтобы на сервер не ушли данные без родителя.

Какую стратегию синхронизации выбрать и что делать с конфликтами?

По умолчанию синхронизируйте в режиме pull + push: сначала заберите изменения сервера, затем отправьте локальные. Для конфликтов лучше опираться на версии (revision/updatedAt) и решать конфликт на уровне полей, потому что добавление фото и правка типа точки обычно не должны мешать друг другу.

Какие экраны и сценарии стоит сделать в первой версии, чтобы реально работало в поле?

Держите первый релиз коротким: список объектов, список/карта точек, карточка точки с фото и заметками, форма добавления измерения и журнал изменений. Валидации делайте на вводе и показывайте предупреждения для подозрительных значений, чтобы не останавливать работу в поле, но и не плодить явные ошибки.

Как быстро протестировать офлайн и синхронизацию и как это ускорить через TakProsto?

Проверяйте сценарий «без сети» целиком: создание объекта, 10–30 точек, несколько фото, редактирование, перезапуск приложения и последующая отправка при появлении интернета без дублей. Если собираете прототип в TakProsto, удобно сначала описать модель и правила синка в Planning mode, затем сгенерировать Flutter-клиент с SQLite и сервер на Go + PostgreSQL, а после — прогнать тест и откатиться снапшотом, если правки сломали офлайн.

Похожие статьи