Seluruh dunia di saku Anda atau cara membuat kartu seluler dalam beberapa hari



Dalam artikel sebelumnya, saya berbicara tentang cara cepat membuat dialer-web. Tetapi bagaimana jika Anda menetapkan tugas yang lebih ambisius - untuk merakit aplikasi Anda sendiri dengan kartu, tanpa iklan dan dengan blackjack? Dan jika hanya dalam beberapa hari?


Ayo lakukan! Saya minta kucing.


Pertama, mari kita cari tahu apa yang harus kita lakukan. Pada output, kami ingin mendapatkan aplikasi dengan data referensi dan peta. Dan untuk bekerja secara offline. Sebagai pengembang, saya hanya tertarik pada peta saja, karena kami sudah tahu cara menunjukkan data referensi. Dan offline adalah batasan yang cukup kuat dalam hal ini, karena tidak banyak perpustakaan yang baik dengan dukungan offline. Oleh karena itu, dalam artikel ini kita akan berkonsentrasi pada peta, tetapi mari kita bicara tentang panduan ini secara sepintas.


Pilih mesin peta


Hal pertama yang harus dilakukan adalah mendapatkan data untuk aplikasi tersebut. Ada banyak sumber di pasaran, gratis dan tidak terlalu. Sebagai permulaan, OpenStreetMap sangat cocok untuk kita sebagai sumber terbuka data peta. Di sana Anda dapat mengambil sejumlah POI untuk direktori kami.


Langkah selanjutnya adalah memilih mesin kartu. Di Internet, ada beberapa dari mereka, bahkan lebih sedikit yang gratis, dan dengan dukungan offline umumnya hanya ada beberapa. Saya sarankan menggunakan opsi yang cukup keren - mapsforge / vtm . Ini adalah mesin OpenGL vektor, sangat cepat, mendukung offline, Android, iOS, berbagai sumber data, gaya khusus, overlay, spidol, model objek 3D bahkan 3D! Sangat, sangat keren.


Repositori memiliki banyak contoh untuk mulai cepat, ada kartu siap pakai, ada plug-in yang memungkinkan Anda untuk merakit kartu Anda sendiri dari data dalam format OSM. Jadi, mari kita mulai!


MapView mapView = findViewById(R.id.map_view); this.map = mapView.map(); File baseMapFile = getMapFile("cyprus.map"); MapFileTileSource tileSource = new MapFileTileSource(); tileSource.setMapFile(baseMapFile.getAbsolutePath()); VectorTileLayer layer = this.map.setBaseMap(tileSource); MapInfo info = tileSource.getMapInfo(); if (info != null) { MapPosition pos = new MapPosition(); pos.setByBoundingBox(info.boundingBox, Tile.SIZE * 4, Tile.SIZE * 4); this.map.setMapPosition(pos); } this.map.setTheme(VtmThemes.DEFAULT); this.map.layers().add(new BuildingLayer(this.map, layer)); this.map.layers().add(new LabelLayer(this.map, layer)); 

Buat sumber data MapFileTileSource, tentukan lokasi file peta. Selain itu, kami menempatkan diri di tengah kotak pembatas yang menarik minat kami, agar tidak berada di suatu tempat di luar lokasi yang dipilih saat aplikasi dimulai. Instal tema default. Tambahkan lapisan rumah dan lapisan tanda tangan. Itu saja. Kami meluncurkan - keajaiban!



Tampaknya lebih cepat dan lebih mudah dan tidak mungkin.


Kami melakukan geocoding


Langkah penting berikutnya adalah menerapkan geocoding. Kartu itu sendiri sudah bagus, tetapi interaktivitas diperlukan. Kami ingin memasuki peta dan melihat informasi tentang objek yang kami tekan. Dan ada beberapa kesulitan. Secara umum, tidak ada geocoding lengkap di perpustakaan kami. Ini mungkin minus terbesarnya. Jika tidak ada yang ditemukan, maka kita dapat memanfaatkan fungsionalitas yang ada.


 //          float touchRadius = TOUCH_RADIUS * CanvasAdapter.getScale(); long mapSize = MercatorProjection.getMapSize((byte) mMap.getMapPosition().getZoomLevel()); double pixelX = MercatorProjection.longitudeToPixelX(p.getLongitude(), mapSize); double pixelY = MercatorProjection.latitudeToPixelY(p.getLatitude(), mapSize); int tileXMin = MercatorProjection.pixelXToTileX(pixelX - touchRadius, (byte) mMap.getMapPosition().getZoomLevel()); int tileXMax = MercatorProjection.pixelXToTileX(pixelX + touchRadius, (byte) mMap.getMapPosition().getZoomLevel()); int tileYMin = MercatorProjection.pixelYToTileY(pixelY - touchRadius, (byte) mMap.getMapPosition().getZoomLevel()); int tileYMax = MercatorProjection.pixelYToTileY(pixelY + touchRadius, (byte) mMap.getMapPosition().getZoomLevel()); Tile upperLeft = new Tile(tileXMin, tileYMin, (byte) mMap.getMapPosition().getZoomLevel()); Tile lowerRight = new Tile(tileXMax, tileYMax, (byte) mMap.getMapPosition().getZoomLevel()); //   ,        MapDatabase mapDatabase = ((MapDatabase) ((OverzoomTileDataSource) tileSource.getDataSource()).getDataSource()); MapReadResult mapReadResult = mapDatabase.readLabels(upperLeft, lowerRight); StringBuilder sb = new StringBuilder(); //   POI     sb.append("*** POI ***"); for (PointOfInterest pointOfInterest : mapReadResult.pointOfInterests) { Point layerXY = new Point(); mMap.viewport().toScreenPoint(pointOfInterest.position, false, layerXY); Point tapXY = new Point(e.getX(), e.getY()); if (layerXY.distance(tapXY) > touchRadius) { continue; } sb.append("\n"); List<Tag> tags = pointOfInterest.tags; for (Tag tag : tags) { sb.append("\n").append(tag.key).append("=").append(tag.value); } } //  ,     sb.append("\n\n").append("*** WAYS ***"); for (Way way : mapReadResult.ways) { if (way.geometryType != GeometryBuffer.GeometryType.POLY || !GeoPointUtils.contains(way.geoPoints[0], p)) { continue; } sb.append("\n"); List<Tag> tags = way.tags; for (Tag tag : tags) { sb.append("\n").append(tag.key).append("=").append(tag.value); } } 

Ternyata relatif bertele-tele. Anda perlu menemukan ubin, mendapatkan cara (dalam terminologi OSM, ini adalah objek linier), dan Anda dapat mengekstrak beberapa atribut dari mereka. Selain cara-cara, dimungkinkan untuk mendapatkan POI, tetapi hanya itu. Anda harus memutar sendiri sisa logikanya: pilih "benar" dari seluruh set objek yang klik klik, saring berdasarkan level zoom. Dan satu hal lagi. Faktanya, kami kehilangan informasi tentang geometri asli dan mendapat tanggapan atas pencarian hanya satu set garis. Jika Anda juga ingin membuat geo-editor, maka ini jelas tidak akan cukup.


Tetapi untuk menunjukkan pendekatannya, semuanya cocok untuk kita.





Geocoding Lanjut


Secara umum, ada opsi yang lebih maju. Untuk ini kita membutuhkan basis kita sendiri. Secara khusus, Anda dapat menggunakan SQLite. Benar, SQLite standar tidak akan cukup bagi kami, dan kami harus membangunnya sendiri dengan menghubungkan plugin RTree untuk pencarian geografis ke sana. Cara melakukan ini, saya sudah memberi tahu di artikel , bagian "Lakukan pencarian yang baik."
Dalam hal ini, kami mendapatkan kontrol penuh atas data, kami dapat menyimpan semua yang diperlukan, dan dalam format yang tepat. Kami juga dapat mempercepat Pencarian Teks Lengkap dan mencari objek geo kami dan perusahaan berdasarkan nama, alamat dan atribut lainnya.


Arahnya adalah:


  1. Kami membuat tabel:
    • objek geo (id, tipe, geometri, atribut)
    • perusahaan (id, atribut, geo_id) dengan referensi ke geometri bangunan tempat ia berada
    • rtree geoindex menyukai ini:
       CREATE VIRTUAL TABLE geo_index USING rtree( id, -- Integer primary key minX, maxX, -- Minimum and maximum X coordinate minY, maxY -- Minimum and maximum Y coordinate ); 
  2. Kami mengisi semuanya dengan data.
  3. Saat Anda mengetuk peta, kami mendapatkan GeoPoint dan menjalankan permintaan:
     SELECT id FROM geo_index WHERE minX>=-81.08 AND maxX<=-80.58 AND minY>=35.00 AND maxY<=35.44 
  4. Langkah terakhir: filter dan pilih objek yang sesuai.

Salah satu opsi implementasi dapat dilihat di repositori .


Akibatnya, kami sudah tahu cara menampilkan peta dan menangani klik. Tidak buruk.


Tambahkan hal-hal kecil yang penting.


Mari kita tambahkan beberapa fitur penting.


Mari kita mulai dengan lokasi saat ini. Di mapsforge / vtm hanya ada yang khusus untuk ini. Lapisan LocationLayer. Penggunaannya sangat sederhana.


 LocationLayer locationLayer = new LocationLayer(this.map); locationLayer.setEnabled(true); //       , ,     GPS GeoPoint initialGeoPoint = this.map.getMapPosition().getGeoPoint(); locationLayer.setPosition(initialGeoPoint.getLatitude(), initialGeoPoint.getLongitude(), 1); this.map.layers().add(locationLayer); 

Hanya ada satu kelemahan - itu adalah riak konstan "titik biru" di perbatasan layar ketika lokasi saat ini berada di luar peta. Kemungkinan besar, dalam proses penggunaan, Anda jarang akan menemukan diri Anda dalam situasi seperti itu, tetapi ini menyebabkan rendering ulang yang konstan, karenanya, memuat prosesor sedikit. Untuk menghilangkan ini sedikit lebih sulit, Anda harus masuk ke shader dan memperbaikinya. Tapi ini sudah untuk perfeksionis. Bagaimana melakukannya - Anda bisa lihat di sini .


Jadi posisinya. Saatnya menambahkan tombol navigasi ke posisi saat ini, seperti pada semua aplikasi pemetaan yang menghargai diri sendiri.


 View vLocation = findViewById(R.id.am_location); vLocation.setOnClickListener(v -> this.map.animator().animateTo(initialGeoPoint)); 

Kami juga membutuhkan tombol zoom.


 View vZoomIn = findViewById(R.id.am_zoom_in); vZoomIn.setOnClickListener(v -> this.map.animator().animateZoom(500, 2, 0, 0)); View vZoomOut = findViewById(R.id.am_zoom_out); vZoomOut.setOnClickListener(v -> this.map.animator().animateZoom(500, 0.5, 0, 0)); 

Dan ceri pada kue adalah kompas.


 View vCompass = findViewById(R.id.am_compass); vCompass.setVisibility(View.GONE); vCompass.setOnClickListener(v -> { MapPosition mapPosition = this.map.getMapPosition(); mapPosition.setBearing(0); this.map.animator().animateTo(500, mapPosition); vCompass.animate().setListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { vCompass.setVisibility(View.GONE); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }).setDuration(500).rotation(0).start(); }); this.map.events.bind((e, mapPosition) -> { if (e == Map.ROTATE_EVENT) { vCompass.setRotation(mapPosition.getBearing()); vCompass.setVisibility(View.VISIBLE); } }); 




Menangkap dunia


Teman, kita ada di garis finish. Tetap menambahkan sentuhan akhir. Kami berencana untuk menangkap dunia, yang berarti bahwa kita perlu menjejalkannya ke dalam aplikasi kita.


Dan hal-hal sedemikian rupa sehingga dengan mesin kami jauh lebih mudah daripada yang terdengar.
Kami perlu sedikit memodifikasi metode pemuatan peta dengan menambahkan MultyMapTileSource ke dalamnya. Ini pada dasarnya adalah pembungkus untuk sumber ubin lain, yang memungkinkan Anda untuk menampilkan semua yang ditambahkan ke dalamnya pada peta sekaligus. Hanya fitur pembunuh. Akibatnya, kami harus menyiapkan peta dunia dengan detail minimal, menambahkannya lebih dulu ke pembungkus kami, dan menggambar sisanya di atas. Selain itu, kami dapat segera menambahkan semua kartu yang kami miliki di katalog dengan peta aplikasi! Cantik, hanya cantik. Dan jangan lupa itu offline :)


 //  - MultiMapFileTileSource mmtilesource = new MultiMapFileTileSource(); File baseMapFile = getMapFile("cyprus.map"); MapFileTileSource tileSource = new MapFileTileSource(); tileSource.setMapFile(baseMapFile.getAbsolutePath()); mmtilesource.add(tileSource); //     MultiMapFileTileSource MapFileTileSource worldTileSource = new MapFileTileSource(); File worldMapFile = getMapFile("world.map"); worldTileSource.setMapFile(worldMapFile.getAbsolutePath()); mmtilesource.add(worldTileSource); //      - VectorTileLayer layer = this.map.setBaseMap(mmtilesource); 


Mungkin kita siap untuk dibebaskan. Kami mengumpulkan bangunan, menaruhnya di pasar, dan mendapatkan bintang yang memang layak diterima :)


Beberapa sendok tar dalam satu tong besar madu


Mesin open source sedang aktif berkembang, tetapi timnya, sejujurnya, agak sederhana. Pada umumnya, ini adalah satu orang dengan nama devemux86 . Dan beberapa pria lagi dari waktu ke waktu.


Terkadang ada artefak dalam rendering, beberapa berkedip dan berkedut. Tetapi saya tidak pernah mengalami masalah kritis dan semakin banyak crash, yang tidak bisa lain kecuali bersukacita.


Ada satu lagi nuansa yang mungkin tidak menyenangkan. Ini adalah gambar fillet dan lingkaran. Contoh tampilannya di tangkapan layar:





Jika dalam geometri awal ada banyak titik (pembulatannya halus), maka pada peta Anda dapat melihat lingkaran yang agak "bersudut" dengan banyak tonjolan kecil dan cekung. Jelas, ini dilakukan demi kinerja dan ukuran file peta, tetapi itu tidak terlihat sangat bagus.


Mungkin ini semua kontra untuk hari ini. Anda memutuskan apakah Anda bisa tinggal bersama mereka atau tidak. Sementara itu, kami telah menggunakan perpustakaan ini selama lebih dari 1,5 tahun, penerbangannya sangat baik, setidaknya di Android.


Ringkasan


Dalam artikel ini, saya telah menunjukkan bahwa masalah yang tidak sepele sekalipun dapat diselesaikan dengan relatif cepat. Anda memiliki kerangka siap pakai, yang dengannya Anda dapat membuat prototipe proyek apa pun yang melibatkan penggunaan peta offline, untuk waktu minimum.


Jika ada minat, pada artikel selanjutnya saya akan menunjukkan cara membuat lantai ala 2GIS. Dan ini sebenarnya jauh lebih sederhana daripada yang terlihat :)

Source: https://habr.com/ru/post/id453182/


All Articles