Хабрахабр

Дубай Молл в смартфоне, или как добавить поэтажный план здания в своё приложение

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

Хотелось бы ещё и местоположение пользователя видеть, но здесь проблема в технической плоскости — нужно оборудование, которое позволит вам получать координаты устройства внутри помещения. Исходная постановка задачи в упрощённом виде: хочется иметь возможность визуализировать схему этажа в вашем мобильном приложении и уметь показывать на нём местоположение конкретной организации. Так что этот аспект оставим за рамками статьи и сосредоточимся на программной части.

Всё зависит от того, какими данными вы обладаете и что конкретно должно уметь приложение. Ниже я покажу вам несколько вариантов, с помощью которых можно реализовать описанные выше требования. И начнём мы с самого простого.

Первый вариант. Готовый API с данными

Первый вариант, который мы рассмотрим, — использование готового виджета от 2ГИС. Описание API можно посмотреть на api.2gis.ru. Он подойдёт вам, если интересующее вас здание уже представлено в 2ГИС, и в здании уже отрисованы этажи. То есть с точки зрения данных всё уже сделано. И самое главное — вы готовы к онлайну, так как этот вариант будет работать исключительно при наличии интернета.

Реализация выполнения в виде веб-виджета, который нужно просто засунуть в WebView. Для отображения этажей в этом случае от вас вообще практически ничего не требуется.

<WebView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/am_webview" android:layout_marginTop="160dp"
/>

Это контейнер, в который мы поместим наш виджет. Инициализируем его следующим образом:

webView = findViewById(R.id.am_webview)
webView.settings.javaScriptEnabled = true
webView.settings.useWideViewPort = true
webView.settings.loadWithOverviewMode = true
//webView.settings.setSupportZoom(true)
//webView.settings.displayZoomControls = true
//webView.settings.builtInZoomControls = true webView.loadUrl("file:///android_asset/map_api.html")
webView.webViewClient = object : WebViewClient() ) }
}

Настраиваем сам webView, обязательно разрешаем JavaScript, javaScriptEnabled = true, так как мы будем взаимодействовать с виджетом через него. При необходимости можно включить скроллбары и кнопки зума (см. закомментированный код), но получается не очень красиво, так что не рекомендую.

В примере выше после загрузки карты мы вызываем метод initMap, определённый в map_api.html, передавая в него идентификатор здания, для которого хотим показать этажи. Самое главное — загрузка HTML с нашим виджетом webView.loadUrl(«file:///android_asset/map_api.html») и подписка на события, если требуется.

Вызывается метод DG. В HTML довольно простой код. В качестве контейнера указываем id, с которым у нас объявлен div в HTML-разметке выше. FloorsWidget.init, в который передаётся json-объект, содержащий данные для инициализации. И в initData передаём здание в теге complexId, и дополнительные параметры отображения виджета, которые можно посмотреть в документации к API. Настраиваем ширину, высоту. В своём примере я использовал Дубай Молл. Идентификатор, кстати, можно подсмотреть в ответе на запрос search, который 2ГИС посылает при клике в интересующее вас здание на 2gis.ru. Но никто не мешает указать любое другое здание с этажами.

Для того, чтобы переместиться к конкретной фирме, вызываем метод showFirm, передавая идентификатор фирмы Последний штрих.

webView.loadUrl("javascript:showFirm('$firmId')")

Готовый пример реализации можно посмотреть на Гитхабе. Всё довольно просто.

Плюсы рассмотренного варианта:

  • готовые данные с планами этажей и данными по фирмам;
  • готовая легковесная реализация на webview;
  • готовый поиск по данным 2ГИС.

Минусы:

  • требуется интернет;
  • необходимы базовые знания HTML и JavaScript;
  • только данные 2ГИС и только те здания, в которых уже есть этажи.

Второй вариант. План этажа в виде картинки

Если вариант с готовыми данными и API вам не подходит, можно воспользоваться следующим.

Если требуется интерактивность вида «ткнул в картинку — открыл карточку объекта», то будет необходим ещё и геокодинг, который нужно будет реализовывать самостоятельно. В этом случае вам понадобится план этажа в виде картинки, скажем, jpeg или png. На этом вопросе подробно останавливаться не будем, надеюсь, здесь всё понятно. Если привязываться к глобальным координатам, то картинка должна быть корректно отмасштабирована и один из углов должен быть притянуть к реальным координатам, чтобы можно было от него вычислять смещения.

И для этого нам идеально подойдёт библиотека TileView. Самое главное — показать эту картинку в приложении. Но нам она также подходит, так как предоставляет возможности по перемещению и зуму, центрированию в указанной позиции, отображению маркеров, преобразованию координат и возможности подписки на различные события. На самом деле, эта библиотека позволяет в принципе отображать картографические тайлы.

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

tileView.setSize(floorWidth, floorHeight)
tileView.setShouldRenderWhilePanning(true) tileView.addDetailLevel(1f, "tiles/floors1/1000/%d_%d.jpg")
tileView.addDetailLevel(0.500f, "tiles/floors1/500/%d_%d.jpg")
tileView.addDetailLevel(0.250f, "tiles/floors1/250/%d_%d.jpg")
tileView.addDetailLevel(0.125f, "tiles/floors1/125/%d_%d.jpg") tileView.defineBounds(0.0, 0.0, floorWidth.toDouble(), floorHeight.toDouble())
tileView.setScaleLimits(0f, 5f)
tileView.setMinimumScaleMode(ZoomPanLayout.MinimumScaleMode.FIT)

Всё, отображение готово. Можно подписываться на события и добавлять необходимую логику. Например, маркер можно отобразить так:

tileView.setMarkerAnchorPoints(-0.5f, -0.5f)
tileView.addMarker(imageView, x, floorHeight - y, null, null)

Полный пример доступен на Гитхабе.

Плюсы:

  • возможность сделать полностью офлайн;
  • относительно простая подготовка данных, план этажа в виде картинки.

Минусы:

  • отсутствие динамической стилизации.

Третий вариант. Векторные данные

Этот вариант — самый продвинутый и самый сложный. Он предполагает наличие у вас подготовленных векторных данных, то есть полностью отрисованных в векторе этажей. Вам понадобятся несколько видов объектов. Площадники организаций, парковок, фудкортов, сцен, катков и так далее. Линейные объекты — в основном, стены, направления потока. Точечные объекты: входы / выходы, лифты, банкоматы и тому подобное.

Вот как выглядит план этажа в «Фиджи», внутренней системе 2ГИС:

Ну а для их визуализации нам подойдёт векторный движок, о котором я рассказывал в предыдущей статье, — mapsforge-vtm.

Для подготовки я взял исходную геометрию здания и порезал её на кусочки, соответствующие компонентам геометрии, исключительно для простоты. Для демонстрации подхода я подготовил тестовые данные: набор площадников и линий для нескольких этажей на примере здания на солнечном острове Кипр. Остальное — дело техники. Как вы понимаете, самое сложное — подготовка качественных данных. Понадобятся кнопки для переключения этажей, подготовленные стили для отрисовки разных площадников и линий, ну и оверлэй для их отрисовки.

Полный код смотреть тут.

В классе FloorData содержится код тестовых геоданных для наших этажей, а класс FloorsManager предназначен для их отрисовки.

В конструкторе определяем стили для площадников и стен:

styles.put(ObjectType.Floor, org.oscim.layers.vector.geometries.Style.builder() .fillColor(Color.GRAY) .build());

А в методе drawFloor определяем логику добавления объектов в слои на карту:

public void drawFloor(int floorId) { hideFloors(); indoorLayer = new CustomVectorLayer(this.map); List<GeoData> geoObjects = this.floorData.getFloorData(floorId); for (GeoData geo: geoObjects) { indoorLayer.add(geo.getGeometry(), styles.get(geo.getObjectType())); } this.map.layers().add(indoorLayer); indoorLayer.update();
}

Тут всё элементарно. Создаём новый слой indoorLayer, добавляем в него заранее подготовленные данные этажа с нужными стилями и добавляем слой на карту this.map.layers().add(indoorLayer).

Для этого есть FloorPickerControl на основе RecyclerView, который делает как раз то, что нужно. Осталось добавить кнопочки для переключения этажей. Не будем тратить на него время, смотрите исходники.

В нём реализовано и редактирование геообъектов. А вот как выглядит Дубай Молл в нашем приложении.

Плюсы:

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

Минусы:

  • сложная подготовка данных.

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

Все ссылочки в одном месте:
Статья про карту в мобиле
API 2GIS
Пример на API 2GIS
Библиотека TileView
Пример работы с TileView
Пример на mapsforge-vtm

Теги
Показать больше

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть