2gis / mobile-sdk-ios-demo

BSD 2-Clause "Simplified" License
13 stars 5 forks source link

Анимированный кластер #201

Open yadik64 opened 1 year ago

yadik64 commented 1 year ago

Добрый день.

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

Просмотрел документацию и тестовый проект, но не нашел способа реализовать подобное. Буду очень благодарен за ответ.

maxal9999 commented 1 year ago

Добрый день. https://docs.2gis.com/ru/ios/sdk/reference/7.3/SimpleClusterObject#nav-lvl1--var%20objects У SimpleClusterObject есть проперти objects со списком входящих в кластер объектов. SimpleMapObject можно скастить к Marker https://docs.2gis.com/ru/ios/sdk/reference/7.3/Marker и получить иконку icon. У сформированного кластера иконку можно менять через https://docs.2gis.com/ru/ios/sdk/reference/7.3/SimpleClusterObject#nav-lvl1--var%20setIcon

То есть таким образом, в SimpleClusterRendererImpl при формировании кластера можно запустить таску на обновление иконки.

Самое интересное - как идентифицировать кластер после его формирования. Нужно в userData установить какой то идентификатор, который нужно сохранить. https://github.com/2gis/mobile-sdk-ios-demo/blob/master/app/Views/DemoPages/Clustering/ClusteringDemoViewModel.swift#L32 Также нужно сохранить позицию камеры, при которой был сформирован кластер. https://docs.2gis.com/ru/ios/sdk/reference/7.3/BaseCamera#nav-lvl1--var%20position

Ну а далее, в таске по обновлению иконки, первый раз находим кластер в MapObjectManager для сохранения ссылки на него. Для этого вызываем https://docs.2gis.com/ru/ios/sdk/reference/7.3/MapObjectManager#nav-lvl1--clusteringObjects Обходим все объекты, сравниваем userData с необходимым нам идентификатором, находим нужный и все. Находить нужно только 1 раз, если в MapObjectManager не изменяется.

yadik64 commented 1 year ago

Добрый день. https://docs.2gis.com/ru/ios/sdk/reference/7.3/SimpleClusterObject#nav-lvl1--var%20objects У SimpleClusterObject есть проперти objects со списком входящих в кластер объектов. SimpleMapObject можно скастить к Marker https://docs.2gis.com/ru/ios/sdk/reference/7.3/Marker и получить иконку icon. У сформированного кластера иконку можно менять через https://docs.2gis.com/ru/ios/sdk/reference/7.3/SimpleClusterObject#nav-lvl1--var%20setIcon

То есть таким образом, в SimpleClusterRendererImpl при формировании кластера можно запустить таску на обновление иконки.

Самое интересное - как идентифицировать кластер после его формирования. Нужно в userData установить какой то идентификатор, который нужно сохранить. https://github.com/2gis/mobile-sdk-ios-demo/blob/master/app/Views/DemoPages/Clustering/ClusteringDemoViewModel.swift#L32 Также нужно сохранить позицию камеры, при которой был сформирован кластер. https://docs.2gis.com/ru/ios/sdk/reference/7.3/BaseCamera#nav-lvl1--var%20position

Ну а далее, в таске по обновлению иконки, первый раз находим кластер в MapObjectManager для сохранения ссылки на него. Для этого вызываем https://docs.2gis.com/ru/ios/sdk/reference/7.3/MapObjectManager#nav-lvl1--clusteringObjects Обходим все объекты, сравниваем userData с необходимым нам идентификатором, находим нужный и все. Находить нужно только 1 раз, если в MapObjectManager не изменяется.

Спасибо за ответ. Я обязательно это попробую. Но у меня есть еще одна проблема с методом renderCluster. Как я заметил он работает не на главном потоке, что приводит к сбою мой механизм формирования иконки для класетра. Сам кластер это SwiftUI.View я передаю в него изображение маркера и кол-во элементов кластера, после чего вызываю метод snapshot() для получения UIImage на основе View. Метод snapshot() может работать только на главном потоке. Так же я заметил что если вернуть SimpleClusterOptions(icon: nil) то маркеры не отображаются на карте даже когда они не в кластере.

@MainActor
    func renderCluster(cluster: SimpleClusterObject) -> SimpleClusterOptions {
        if let annotation = cluster.objects.first?.userData as? ChildAnnotation {

            let uiImage = YClusterAnnotationView(image: annotation.image,
                                                 count: Int(cluster.objectCount))
                .snapshot()

            var icon = self.imageFactory.make(image: uiImage)

            return SimpleClusterOptions(
                icon: icon,
                iconMapDirection: nil,
                text: nil,
                textStyle: nil,
                iconWidth: LogicalPixel(30.0),
                userData: "",
                zIndex: ZIndex(value: 6)
            )
        }

        return SimpleClusterOptions(icon: nil)
    }

Есть ли возможность вызвать renderCluster на главном потоке?

maxal9999 commented 1 year ago

Есть ли возможность вызвать renderCluster на главном потоке?

Нет, такой возможности нет. Как вариант, можно кластеру проставить временно заглушку, отправить задачу на главный поток, и установить иконку уже потом.

yadik64 commented 1 year ago

Есть ли возможность вызвать renderCluster на главном потоке?

Нет, такой возможности нет. Как вариант, можно кластеру проставить временно заглушку, отправить задачу на главный поток, и установить иконку уже потом.

Если формирование кластера идет не на главном потоке есть хотя бы какой то callback сообщающий о том что кластер сформирован и можно его изменить?

maxal9999 commented 1 year ago

Например, можно заложиться на https://docs.2gis.com/ru/ios/sdk/reference/7.3/Map#nav-lvl1--var%20dataLoadingStateChannel То есть во время создания кластера он будет создаст, когда закончатся любые загрузки карты.

maxal9999 commented 12 months ago

Версия 7.4.0 опубликована. Теперь все ок?

yadik64 commented 12 months ago

https://github.com/2gis/mobile-sdk-ios-demo/assets/45825824/aa60b003-8caf-4477-aa2a-1aa0bdc8c276

Версия 7.4.0 опубликована. Теперь все ок?

Да, смена иконки работает. Спасибо. Но проблему с анимацией кластера до конца решить не удалось. При смене зума у вас почему то создается еще один кластер, хотя это тот же самый кластер только с другим зумом. И это приводит к проблемам. Например если выбрать кластер и поменять ему иконку, а потом поменять зум иконка вернется в состояние "не выбран", вернем зум назад и кластер сново с иконкой в состоянии "выбран". Это все можно видеть в вашем тестовом проекте. Формирование кластера не на главном потоке, отсутствие кэлбэков, разные экземпляры одного и того же кластера с разным зумированием делает их практически невозможными для кастомизации. Хотелось бы видеть работу с кластерами пусть не такую как в MapKit, но хотябы как в YandexMapsMobile

maxal9999 commented 11 months ago

У нас кластеризация быстрее работает, чем у Яндекса или Гугла, за счет как раз выстраивания дерева кластеризация сразу же после добавления объектов. ЧТо касается того, что на новом зум уровне это другой кластер - это правильно, так как новый кластер на другом зум уровне формируется как суперпозиция всех кластеров на уровень ниже.

Формирование кластера не на главном потоке, отсутствие кэлбэков, разные экземпляры одного и того же кластера с разным зумированием делает их практически невозможными для кастомизации.

Это очень спорный месс, так как:

  1. Не на главном потоке - мы никак не вешаем UI.
  2. Разные экземпляры - я объяснил выше почему так. Но на каждое формирование на каждом зуме все равно вызывается функция по формированию кластера.
  3. Отсутствие колбэков - вот тут можно подумать.

Мы готовы доработать еще кластеризацию, даже в 7 версию. Но нужен список требований того, что сейчас не хватает. Добавление колбэка о том, что кластер сформирован - этого достаточно?