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

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.


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

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

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



Первый вариант. Готовый 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() {
	override fun onPageFinished(view: WebView, url: String) {
		super.onPageFinished(view, url)
		val js = "initMap('13933647002593772');" // The Dubai Mall

		webView.evaluateJavascript(js, ValueCallback {
		})
	}
}

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

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

В HTML довольно простой код. Вызывается метод DG.FloorsWidget.init, в который передаётся json-объект, содержащий данные для инициализации. В качестве контейнера указываем id, с которым у нас объявлен div в HTML-разметке выше. Настраиваем ширину, высоту. И в 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
Источник: https://habr.com/ru/company/2gis/blog/476816/


Интересные статьи

Интересные статьи

Привет, сегодня я расскажу вам о разработке своей первой игры на Android, публикации ее в Google Play, закупке рекламы в Google Ads и доходе. Я советую эту статью к прочт...
Современные цифровые технологии позволяют легко "захватывать" информацию из любых источников. Мы пропускаем через себя много информации, но мало из этого, что приносит на...
Странные аттракторы — это области, которые часто возникают в различных физических системах. Можно сказать, что это область притяжения, к которой стремятся траектории из некоторой окре...
Важный компонент защиты от несанкционированного доступа Каждому разумному человеку есть, что скрывать. Это нормально. Никто не хочет, чтобы утекли его приватные ключи для доступа...
Привет всем! В этой статье я хотел бы показать вам, как создать Flutter приложение, используя Redux. Если вы не знаете, что такое Flutter, то это — SDK с открытым исходным кодом для создания ...