สร้างแผนที่ 3 มิติแผนที่แรกด้วย SwiftUI

1. ก่อนเริ่มต้น

โค้ดแล็บนี้จะสอนวิธีสร้างแอปแผนที่ 3 มิติใน SwiftUI โดยใช้ Maps 3D SDK สำหรับ iOS

แอปแสดงแผนที่ 3 มิติของซานฟรานซิสโก

คุณจะได้เรียนรู้:

  • วิธีควบคุมกล้องเพื่อดูสถานที่และบินไปรอบๆ แผนที่
  • วิธีเพิ่มเครื่องหมายและโมเดล
  • วิธีวาดเส้นและรูปหลายเหลี่ยม
  • วิธีจัดการกับการคลิกของเครื่องหมายสถานที่โดยผู้ใช้

ข้อกำหนดเบื้องต้น

  • โปรเจ็กต์ Google Console ที่เปิดใช้การเรียกเก็บเงิน
  • คีย์ API ซึ่งอาจจํากัดไว้สําหรับ Maps 3D SDK สําหรับ iOS
  • ความรู้พื้นฐานเกี่ยวกับการพัฒนา iOS ด้วย SwiftUI

สิ่งที่ต้องทำ

  • ตั้งค่า Xcode และนํา SDK มาใช้โดยใช้ Swift Package Manager
  • กำหนดค่าแอปให้ใช้คีย์ API
  • เพิ่มแผนที่ 3 มิติพื้นฐานลงในแอป
  • ควบคุมกล้องให้บินไปยังและรอบๆ สถานที่ที่ต้องการ
  • เพิ่มเครื่องหมาย เส้น รูปหลายเหลี่ยม และโมเดลลงในแผนที่

สิ่งที่คุณต้องมี

  • Xcode 15 ขึ้นไป

2. ตั้งค่า

ในขั้นตอนการเปิดใช้ต่อไปนี้ คุณจะต้องเปิดใช้ Maps 3D SDK สำหรับ iOS

ตั้งค่า Google Maps Platform

หากยังไม่มีบัญชี Google Cloud Platform และโปรเจ็กต์ที่เปิดใช้การเรียกเก็บเงิน โปรดดูคู่มือเริ่มต้นใช้งาน Google Maps Platform เพื่อสร้างบัญชีการเรียกเก็บเงินและโปรเจ็กต์

  1. ใน Cloud Console ให้คลิกเมนูแบบเลื่อนลงของโปรเจ็กต์ แล้วเลือกโปรเจ็กต์ที่ต้องการใช้สำหรับโค้ดแล็บนี้

  1. เปิดใช้ Google Maps Platform API และ SDK ที่จําเป็นสําหรับโค้ดแล็บนี้ใน Google Cloud Marketplace โดยทำตามขั้นตอนในวิดีโอนี้หรือเอกสารประกอบนี้
  2. สร้างคีย์ API ในหน้าข้อมูลเข้าสู่ระบบของ Cloud Console คุณสามารถทำตามขั้นตอนในวิดีโอนี้หรือเอกสารประกอบนี้ คำขอทั้งหมดที่ส่งไปยัง Google Maps Platform ต้องใช้คีย์ API

เปิดใช้ Maps 3D SDK สำหรับ iOS

คุณดู Maps 3D SDK สําหรับ iOS ได้โดยใช้ลิงก์เมนู Google Maps Platform > API และบริการในคอนโซล

คลิก "เปิดใช้" เพื่อเปิดใช้ API ในโปรเจ็กต์ที่เลือก

เปิดใช้ Maps 3D SDK ในคอนโซล Google

3. สร้างแอป SwiftUI พื้นฐาน

หมายเหตุ: คุณดูโค้ดโซลูชันของแต่ละขั้นตอนได้ในที่เก็บแอปตัวอย่างของ Codelab ใน GitHub

สร้างแอปใหม่ใน Xcode

โค้ดสําหรับขั้นตอนนี้จะอยู่ในโฟลเดอร์ GoogleMaps3DDemo ใน GitHub

เปิด Xcode และสร้างแอปใหม่ ระบุ SwiftUI

ตั้งชื่อแอปว่า GoogleMaps3DDemo ที่มีชื่อแพ็กเกจ com.example.GoogleMaps3DDemo

นําเข้าไลบรารี GoogleMaps3D ไปยังโปรเจ็กต์

เพิ่ม SDK ลงในโปรเจ็กต์โดยใช้ Swift Package Manager

ในโปรเจ็กต์หรือเวิร์กスペース Xcode ให้ไปที่ File > Add Package Dependencies ป้อน https://github.com/googlemaps/ios-maps-3d-sdk เป็น URL, กด Enter เพื่อดึงข้อมูลแพ็กเกจ แล้วคลิก "เพิ่มแพ็กเกจ"

จากหน้าต่าง "เลือกผลิตภัณฑ์แพ็กเกจ" ให้ยืนยันว่าระบบจะเพิ่ม GoogleMaps3D ไปยังเป้าหมายหลักที่คุณกำหนด เมื่อดำเนินการเสร็จแล้ว ให้คลิก "เพิ่มแพ็กเกจ"

หากต้องการยืนยันการติดตั้ง ให้ไปที่แผงทั่วไปของเป้าหมาย คุณควรเห็นแพ็กเกจที่ติดตั้งแล้วในเฟรมเวิร์ก ไลบรารี และเนื้อหาที่ฝัง นอกจากนี้ คุณยังดูส่วน "แพ็กเกจที่ต้องพึ่งพา" ของ Project Navigator เพื่อยืนยันแพ็กเกจและเวอร์ชันของแพ็กเกจได้ด้วย

เพิ่มคีย์ API

คุณสามารถฝังคีย์ API ไว้ในโค้ดของแอปได้ แต่วิธีนี้ไม่ใช่แนวทางปฏิบัติแนะนำ การเพิ่มไฟล์การกําหนดค่าช่วยให้คุณเก็บคีย์ API ไว้เป็นความลับและหลีกเลี่ยงการตรวจสอบคีย์ดังกล่าวในระบบควบคุมแหล่งที่มา

สร้างไฟล์การกําหนดค่าใหม่ในโฟลเดอร์รูทของโปรเจ็กต์

ใน Xcode ให้ตรวจสอบว่าคุณกำลังดูหน้าต่างเครื่องมือสำรวจโปรเจ็กต์ คลิกขวาที่รูทของโปรเจ็กต์แล้วเลือก "ไฟล์ใหม่จากเทมเพลต" เลื่อนจนกว่าจะเห็น "ไฟล์การตั้งค่าการกําหนดค่า" เลือกตัวเลือกนี้ แล้วคลิก "ถัดไป" ตั้งชื่อไฟล์เป็น Config.xcconfig และตรวจสอบว่าได้เลือกโฟลเดอร์รูทของโปรเจ็กต์แล้ว คลิก "สร้าง" เพื่อสร้างไฟล์

ในเครื่องมือแก้ไข ให้เพิ่มบรรทัดลงในไฟล์การกําหนดค่า ดังนี้ MAPS_API_KEY = YOUR_API_KEY

แทนที่ YOUR_API_KEY ด้วยคีย์ API ของคุณ

เพิ่มการตั้งค่านี้ลงใน Info.plist

โดยเลือกรูทโปรเจ็กต์แล้วคลิกแท็บ "ข้อมูล"

เพิ่มพร็อพเพอร์ตี้ใหม่ชื่อ MAPS_API_KEY ที่มีค่าเป็น $(MAPS_API_KEY)

โค้ดตัวอย่างแอปมีไฟล์ Info.plist ที่ระบุพร็อพเพอร์ตี้นี้

เพิ่มแผนที่

เปิดไฟล์ชื่อ GoogleMaps3DDemoApp.swift นี่เป็นจุดเข้าและการนำทางหลักของแอป

ซึ่งจะเรียก ContentView() ซึ่งแสดงข้อความ Hello World

เปิด ContentView.swift ในเครื่องมือแก้ไข

เพิ่มคำสั่ง import สำหรับ GoogleMaps3D

ลบโค้ดภายในโค้ดบล็อก var body: some View {} ประกาศ Map() ใหม่ภายใน body

การกําหนดค่าขั้นต่ำที่คุณต้องใช้ในการเริ่มต้น Map คือ MapMode ค่านี้มีค่าที่เป็นไปได้ 2 ค่า ได้แก่

  • .hybrid - ภาพถ่ายจากดาวเทียมที่มีถนนและป้ายกำกับ หรือ
  • .satellite - ภาพถ่ายจากดาวเทียมเท่านั้น

เลือก .hybrid เลย

ไฟล์ ContentView.swift ควรมีลักษณะดังนี้

import GoogleMaps3D
import SwiftUI

@main
struct ContentView: View {
    var body: some View {
      Map(mode: .hybrid)
    }
}

ตั้งค่าคีย์ API

คุณต้องตั้งค่าคีย์ API ก่อนแผนที่จะเริ่มต้น

ซึ่งทำได้โดยการตั้งค่า Map.apiKey ในตัวแฮนเดิลเหตุการณ์ init() ของ View ที่มีแผนที่ นอกจากนี้ คุณยังตั้งค่าใน GoogleMaps3DDemoApp.swift ก่อนที่จะโทรหา ContentView() ได้ด้วย

ใน GoogleMaps3DDemoApp.swift ให้ตั้งค่า Map.apiKey ในเครื่องจัดการเหตุการณ์ onAppear ของ WindowGroup

ดึงข้อมูลคีย์ API จากไฟล์การกําหนดค่า

ใช้ Bundle.main.infoDictionary เพื่อเข้าถึงการตั้งค่า MAPS_API_KEY ที่คุณสร้างไว้ในไฟล์การกําหนดค่า

import GoogleMaps3D
import SwiftUI

@main
struct GoogleMaps3DDemoApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
    .onAppear {
      guard let infoDictionary: [String: Any] = Bundle.main.infoDictionary else {
        fatalError("Info.plist not found")
      }
      guard let apiKey: String = infoDictionary["MAPS_API_KEY"] as? String else {
        fatalError("MAPS_API_KEY not set in Info.plist")
      }
      Map.apiKey = apiKey
    }
  }
}

สร้างและเรียกใช้แอปเพื่อตรวจสอบว่าโหลดได้อย่างถูกต้อง คุณจะเห็นแผนที่โลก

แผนที่ 3 มิติที่แสดงโลก

4. ใช้กล้องเพื่อควบคุมมุมมองแผนที่

สร้างออบเจ็กต์สถานะกล้อง

มุมมองแผนที่ 3 มิติจะควบคุมโดยคลาส Camera ในขั้นตอนนี้ คุณจะได้เรียนรู้วิธีระบุตำแหน่ง ระดับความสูง ทิศทาง การเอียง การพลิก และระยะเพื่อปรับแต่งมุมมองแผนที่

มุมมองแผนที่ 3 มิติของซานฟรานซิสโก

สร้างคลาส Helpers เพื่อจัดเก็บการตั้งค่ากล้อง

เพิ่มไฟล์ว่างใหม่ชื่อ MapHelpers.swift ในไฟล์ใหม่ ให้นำเข้า GoogleMaps3D และเพิ่มส่วนขยายลงในคลาส Camera เพิ่มตัวแปรชื่อ sanFrancisco เริ่มต้นตัวแปรนี้เป็นออบเจ็กต์ Camera ใหม่ วางกล้องที่ latitude: 37.39, longitude: -122.08

import GoogleMaps3D

extension Camera {
 public static var sanFrancisco: Camera = .init(latitude: 37.39, longitude: -122.08)
}

เพิ่มมุมมองใหม่ลงในแอป

สร้างไฟล์ใหม่ชื่อ CameraDemo.swift เพิ่มเค้าโครงพื้นฐานของมุมมอง SwiftUI ใหม่ลงในไฟล์

เพิ่มตัวแปร @State ชื่อ camera ประเภท Camera เริ่มต้นใช้งานกับกล้อง sanFrancisco ที่คุณเพิ่งกำหนด

การใช้ @State ช่วยให้คุณเชื่อมโยงแผนที่กับสถานะของกล้องและใช้แผนที่เป็นแหล่งข้อมูลที่ถูกต้องได้

@State var camera: Camera = .sanFrancisco

เปลี่ยนการเรียกใช้ฟังก์ชัน Map() ให้รวมพร็อพเพอร์ตี้ camera ใช้การเชื่อมโยงสถานะกล้อง $camera เพื่อเริ่มต้นค่าของพร็อพเพอร์ตี้ camera ให้กับออบเจ็กต์ @State ของกล้อง (.sanFrancisco)

import SwiftUI
import GoogleMaps3D

struct CameraDemo: View {
  @State var camera: Camera = .sanFrancisco
  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid)
    }
  }
}

เพิ่ม UI การนําทางพื้นฐานลงในแอป

เพิ่ม NavigationView ไปยังจุดแรกเข้าของแอปหลัก GoogleMaps3DDemoApp.swift

ซึ่งจะช่วยให้ผู้ใช้เห็นรายการเดโมและคลิกแต่ละรายการเพื่อเปิดได้

แก้ไข GoogleMaps3DDemoApp.swift เพื่อเพิ่ม NavigationView ใหม่

เพิ่ม List ที่มีประกาศ NavigationLink 2 รายการ

NavigationLink รายการแรกควรเปิด ContentView() พร้อมTextคำอธิบาย Basic Map

NavigationLink ตัวที่ 2 ควรเปิด CameraDemo()

...
      NavigationView {
        List {
          NavigationLink(destination: ContentView()) {
            Text("Basic Map")
          }
          NavigationLink(destination: CameraDemo()) {
            Text("Camera Demo")
          }
        }
      }
...

เพิ่มตัวอย่าง Xcode

การแสดงตัวอย่างเป็นฟีเจอร์ที่มีประสิทธิภาพของ Xcode ที่ช่วยให้คุณดูและโต้ตอบกับแอปขณะทำการเปลี่ยนแปลงได้

หากต้องการเพิ่มตัวอย่างเพลง ให้เปิด CameraDemo.swift เพิ่มโค้ดบล็อก #Preview {} ไว้นอก struct

#Preview {
 CameraDemo()
}

เปิดหรือรีเฟรชบานหน้าต่างแสดงตัวอย่างใน Xcode แผนที่ควรแสดงซานฟรานซิสโก

ตั้งค่ามุมมอง 3 มิติที่กำหนดเอง

คุณสามารถระบุพารามิเตอร์เพิ่มเติมเพื่อควบคุมกล้องได้ ดังนี้

  • heading: ทิศทางเป็นองศาจากเหนือเพื่อเล็งกล้อง
  • tilt: มุมเอียงเป็นองศา โดย 0 หมายถึงเหนือศีรษะโดยตรง และ 90 หมายถึงมองในแนวนอน
  • roll: มุมของการหมุนรอบระนาบแนวตั้งของกล้องเป็นองศา
  • range: ระยะทางของกล้องเป็นเมตรจากตำแหน่งละติจูด ลองจิจูด
  • altitude: ความสูงของกล้องเหนือระดับน้ำทะเล

หากคุณไม่ได้ระบุพารามิเตอร์เพิ่มเติมเหล่านี้ ระบบจะใช้ค่าเริ่มต้น

หากต้องการให้มุมมองกล้องแสดงข้อมูล 3 มิติมากขึ้น ให้ตั้งค่าพารามิเตอร์เริ่มต้นให้แสดงมุมมองที่เอียงและใกล้ขึ้น

แก้ไข Camera ที่คุณกำหนดไว้ใน MapHelpers.swift ให้รวมค่าสำหรับ altitude, heading, tilt, roll และ range

public static var sanFrancisco: Camera = .init(
  latitude: 37.7845812,
  longitude: -122.3660241,
  altitude: 585,
  heading: 288.0,
  tilt: 75.0,
  roll: 0.0,
  range: 100)

สร้างและเรียกใช้แอปเพื่อดูและสำรวจมุมมอง 3 มิติแบบใหม่

5. ภาพเคลื่อนไหวพื้นฐานของกล้อง

จนถึงตอนนี้คุณได้ใช้กล้องเพื่อระบุตำแหน่งเดียวด้วยการเอียง ระดับความสูง ทิศทาง และระยะ ในขั้นตอนนี้ คุณจะได้เรียนรู้วิธีย้ายมุมมองกล้องด้วยการทำภาพเคลื่อนไหวของพร็อพเพอร์ตี้เหล่านี้จากสถานะเริ่มต้นไปยังสถานะใหม่

แผนที่ 3 มิติของซีแอตเทิล

บินไปยังสถานที่

คุณจะใช้เมธอด Map.flyCameraTo() เพื่อแสดงภาพเคลื่อนไหวของกล้องจากตำแหน่งเริ่มต้นไปยังตำแหน่งใหม่

เมธอด flyCameraTo() มีพารามิเตอร์หลายรายการ ดังนี้

  • Camera ที่แสดงตำแหน่งสิ้นสุด
  • duration: ระยะเวลาที่ภาพเคลื่อนไหวจะแสดง หน่วยเป็นวินาที
  • trigger: ออบเจ็กต์ที่สังเกตได้ซึ่งจะทริกเกอร์ภาพเคลื่อนไหวเมื่อสถานะเปลี่ยนแปลง
  • completion: โค้ดที่จะดำเนินการเมื่อภาพเคลื่อนไหวเสร็จสมบูรณ์

กําหนดสถานที่ที่จะบินไป

เปิดไฟล์ MapHelpers.swift

กำหนดออบเจ็กต์กล้องใหม่เพื่อแสดงซีแอตเทิล

public static var seattle: Camera = .init(latitude:
47.6210296,longitude: -122.3496903, heading: 149.0, tilt: 77.0, roll: 0.0, range: 4000)

เพิ่มปุ่มเพื่อเรียกให้ภาพเคลื่อนไหวทำงาน

เปิด CameraDemo.swift ประกาศตัวแปรบูลีนใหม่ภายใน struct

ตั้งชื่อว่า animate โดยมีค่าเริ่มต้นเป็น false

@State private var animate: Bool = false

เพิ่ม Button ไว้ใต้ VStack Button จะเริ่มภาพเคลื่อนไหวของแผนที่

ตั้งชื่อ Button ให้เหมาะสม เช่น "เริ่มบิน"Text

import SwiftUI
import GoogleMaps3D

struct CameraDemo: View {
  @State var camera:Camera = .sanFrancisco
  @State private var animate: Bool = false

  var body: some View {
    VStack{
      Map(camera: $camera, mode: .hybrid)
      Button("Start Flying") {
      }
    }
  }
}

ใน Button Closure ให้เพิ่มโค้ดเพื่อสลับสถานะของตัวแปร animate

      Button("Start Flying") {
        animate.toggle()
      }

เริ่มภาพเคลื่อนไหว

เพิ่มโค้ดเพื่อเรียกใช้ภาพเคลื่อนไหว flyCameraTo() เมื่อสถานะของตัวแปร animate เปลี่ยนแปลง

  var body: some View {
    VStack{
      Map(camera: $camera, mode: .hybrid)
        .flyCameraTo(
          .seattle,
          duration: 5,
          trigger: animate,
          completion: {  }
        )
      Button("Start Flying") {
        animate.toggle()
      }
    }
  }

บินรอบสถานที่

คุณสามารถบินรอบสถานที่โดยใช้วิธีการ Map.flyCameraAround() โดยเมธอดนี้ใช้พารามิเตอร์หลายรายการ ดังนี้

  • Camera ที่กําหนดตําแหน่งและมุมมอง
  • duration วินาที
  • rounds: จำนวนครั้งที่แสดงภาพเคลื่อนไหวซ้ำ
  • trigger: ออบเจ็กต์ที่สังเกตได้ซึ่งจะทริกเกอร์ภาพเคลื่อนไหว
  • callback: โค้ดที่จะทำงานเมื่อภาพเคลื่อนไหวทำงาน

กําหนดตัวแปร @State ใหม่ชื่อ flyAround ที่มีค่าเริ่มต้นเป็น false

เมื่อดำเนินการเสร็จแล้ว ให้เพิ่มการเรียกใช้ flyCameraAround() หลังการเรียกใช้เมธอด flyCameraTo() ทันที

ระยะเวลาของภาพพารามิเตอร์ควรค่อนข้างนานเพื่อให้มุมมองเปลี่ยนอย่างราบรื่น

อย่าลืมเรียกใช้ภาพเคลื่อนไหว flyCameraAround() โดยการเปลี่ยนสถานะของออบเจ็กต์ทริกเกอร์เมื่อ flyCameraTo() เสร็จสมบูรณ์

โค้ดของคุณควรมีลักษณะดังนี้

import SwiftUI
import GoogleMaps3D

struct CameraDemo: View {
  @State var camera:Camera = .sanFrancisco
  @State private var animate: Bool = false
  @State private var flyAround: Bool = false

  var body: some View {
    VStack{
      Map(camera: $camera, mode: .hybrid)
        .flyCameraTo(
          .seattle,
          duration: 5,
          trigger: animate,
          completion: { flyAround = true }
        )
        .flyCameraAround(
          .seattle,
          duration: 15,
          rounds: 0.5,
          trigger: flyAround,
          callback: {  }
        )
      Button("Start Flying") {
        animate.toggle()
      }
    }
  }
}

#Preview {
  CameraDemo()
}

แสดงตัวอย่างหรือเรียกใช้แอปเพื่อดูว่ากล้องโฟกัสไปยังจุดหมายต่างๆ เมื่อภาพเคลื่อนไหว flyCameraTo() แสดงเสร็จแล้ว

6. เพิ่มเครื่องหมายลงในแผนที่

ในขั้นตอนนี้ คุณจะได้เรียนรู้วิธีวาดหมุดบนแผนที่

คุณจะต้องสร้างออบเจ็กต์ Marker และเพิ่มลงในแผนที่ SDK จะใช้ไอคอนเริ่มต้นสำหรับเครื่องหมาย สุดท้าย คุณจะต้องปรับความสูงของตัวทำเครื่องหมายและพร็อพเพอร์ตี้อื่นๆ เพื่อเปลี่ยนลักษณะการแสดงผล

แผนที่ 3 มิติที่แสดงเครื่องหมายบนแผนที่

สร้างมุมมอง SwiftUI ใหม่สําหรับการสาธิตเครื่องหมาย

เพิ่มไฟล์ Swift ใหม่ลงในโปรเจ็กต์ ตั้งชื่อว่า MarkerDemo.swift

เพิ่มโครงร่างของมุมมอง SwiftUI และเริ่มต้นแผนที่เช่นเดียวกับใน CameraDemo

import SwiftUI
import GoogleMaps3D

struct MarkerDemo: View {
  @State var camera: Camera = .sanFrancisco
  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid)
    }
  }
}

เริ่มต้นออบเจ็กต์เครื่องหมาย

ประกาศตัวแปรเครื่องหมายใหม่ชื่อ mapMarker ที่ด้านบนของบล็อกโค้ด struct ใน MarkerDemo.swift

วางคำจำกัดความในบรรทัดใต้ประกาศ camera โค้ดตัวอย่างนี้จะเริ่มต้นพร็อพเพอร์ตี้ทั้งหมดที่ใช้ได้

  @State var mapMarker: Marker = .init(
    position: .init(
      latitude: 37.8044862,
      longitude: -122.4301493,
      altitude: 0.0),
    altitudeMode: .absolute,
    collisionBehavior: .required,
    extruded: false,
    drawsWhenOccluded: true,
    sizePreserved: true,
    zIndex: 0,
    label: "Test"
  )

เพิ่มเครื่องหมายลงในแผนที่

หากต้องการวาดเครื่องหมาย ให้เพิ่มเครื่องหมายนั้นลงในการปิดที่เรียกใช้เมื่อสร้างแผนที่

struct MarkerDemo: View {
  @State var camera: Camera = .sanFrancisco
  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid) {
        mapMarker
      }
    }
  }
}

เพิ่ม NavigationLink ใหม่ไปยัง GoogleMaps3DDemoApp.swift โดยมีปลายทางเป็น MarkerDemo() และ Text อธิบายว่า "การสาธิตเครื่องหมาย"

...
      NavigationView {
        List {
          NavigationLink(destination: Map()) {
            Text("Basic Map")
          }
          NavigationLink(destination: CameraDemo()) {
            Text("Camera Demo")
          }
          NavigationLink(destination: MarkerDemo()) {
            Text("Marker Demo")
          }
        }
      }
...

ดูตัวอย่างและเรียกใช้แอป

รีเฟรชตัวอย่างหรือเรียกใช้แอปเพื่อดูเครื่องหมาย

เครื่องหมายแบบยื่นออกมา

วางเครื่องหมายเหนือพื้นหรือเมช 3 มิติได้โดยใช้ altitude และ altitudeMode

คัดลอกการประกาศ mapMarker ใน MarkerDemo.swift ไปยังตัวแปร Marker ใหม่ชื่อ extrudedMarker

ตั้งค่า altitude เป็นค่าที่ไม่ใช่ 0 โดย 50 ก็เพียงพอแล้ว

เปลี่ยน altitudeMode เป็น .relativeToMesh และตั้งค่า extruded เป็น true ใช้ latitude และ longitude จากข้อมูลโค้ดที่นี่เพื่อวางเครื่องหมายบนยอดตึกสูง

  @State var extrudedMarker: Marker = .init(
    position: .init(
      latitude: 37.78980534,
      longitude:  -122.3969349,
      altitude: 50.0),
    altitudeMode: .relativeToMesh,
    collisionBehavior: .required,
    extruded: true,
    drawsWhenOccluded: true,
    sizePreserved: true,
    zIndex: 0,
    label: "Extruded"
  )

เรียกใช้หรือแสดงตัวอย่างแอปอีกครั้ง เครื่องหมายควรปรากฏบนอาคาร 3 มิติ

7. เพิ่มโมเดลลงในแผนที่

คุณสามารถเพิ่ม Model ได้โดยใช้วิธีเดียวกับ Marker คุณจะต้องมีไฟล์โมเดลที่เข้าถึงได้โดยใช้ URL หรือเพิ่มเป็นไฟล์ในเครื่องในโปรเจ็กต์ ในขั้นตอนนี้ เราจะใช้ไฟล์ในเครื่องซึ่งคุณสามารถดาวน์โหลดได้จากที่เก็บ GitHub สําหรับโค้ดแล็บนี้

แผนที่ 3 มิติของซานฟรานซิสโกพร้อมโมเดลบอลลูนร้อน

เพิ่มไฟล์โมเดลลงในโปรเจ็กต์

สร้างโฟลเดอร์ใหม่ในโปรเจ็กต์ Xcode ชื่อ Models

ดาวน์โหลดโมเดลจากที่เก็บแอปตัวอย่างของ GitHub เพิ่มลงในโปรเจ็กต์โดยลากไปยังโฟลเดอร์ใหม่ในมุมมองโปรเจ็กต์ Xcode

ตรวจสอบว่าคุณได้ตั้งค่าเป้าหมายเป็นเป้าหมายหลักสําหรับแอป

ตรวจสอบการตั้งค่าระยะการสร้าง > คัดลอกทรัพยากร Bundle สำหรับโปรเจ็กต์ ไฟล์โมเดลควรอยู่ในรายการทรัพยากรที่คัดลอกไปยังกลุ่ม หากไม่เห็น ให้คลิก "+" เพื่อเพิ่ม

เพิ่มโมเดลลงในแอป

สร้างไฟล์ SwiftUI ใหม่ชื่อ ModelDemo.swift

เพิ่มคำสั่ง import สำหรับ SwiftUI และ GoogleMaps3D เช่นเดียวกับในขั้นตอนก่อนหน้า

ประกาศ Map ภายใน VStack ใน body

import SwiftUI
import GoogleMaps3D

struct ModelDemo: View {
  @State var camera: Camera = .sanFrancisco
  
  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid) {
        
      }
    }
  }
}

รับเส้นทางโมเดลจาก Bundle เพิ่มโค้ดนี้ไว้นอก struct

private let fileUrl = Bundle.main.url(forResource: "balloon", withExtension: "glb")

ประกาศตัวแปรสําหรับโมเดลภายในโครงสร้าง

ระบุค่าเริ่มต้นในกรณีที่ไม่ได้ระบุ fileUrl

  @State var balloonModel: Model = .init(
    position: .init(
      latitude: 37.791376,
      longitude: -122.397571,
      altitude: 300.0),
    url: URL(fileURLWithPath: fileUrl?.relativePath ?? ""),
    altitudeMode: .absolute,
    scale: .init(x: 5, y: 5, z: 5),
    orientation: .init(heading: 0, tilt: 0, roll: 0)
  )

3. ใช้โมเดลกับแผนที่

เช่นเดียวกับการเพิ่ม Marker เพียงระบุข้อมูลอ้างอิงถึง Model ในการประกาศ Map

  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid) {
        balloonModel
      }
    }
  }

ดูตัวอย่างและเรียกใช้แอป

เพิ่ม NavigationLink ใหม่ไปยัง GoogleMaps3DDemoApp.swift โดยมีปลายทางเป็น ModelDemo() และ Text "Model Demo"

...
          NavigationLink(destination: ModelDemo()) {
            Text("Model Demo")
          }
...

รีเฟรชตัวอย่างหรือเรียกใช้แอปเพื่อดูโมเดล

8. วาดเส้นและรูปหลายเหลี่ยมบนแผนที่

ในขั้นตอนนี้ คุณจะได้เรียนรู้วิธีเพิ่มเส้นและรูปร่างรูปหลายเหลี่ยมลงในแผนที่ 3 มิติ

คุณจะกําหนดรูปทรงเป็นอาร์เรย์ของออบเจ็กต์ LatLngAltitude เพื่อลดความซับซ้อน ในแอปพลิเคชันจริง ระบบอาจโหลดข้อมูลจากไฟล์ การเรียก API หรือฐานข้อมูล

แผนที่ 3 มิติของซานฟรานซิสโกแสดงรูปหลายเหลี่ยม 2 รูปและเส้นประกอบ

สร้างออบเจ็กต์รูปร่างเพื่อจัดการข้อมูลรูปร่าง

เพิ่มคําจํากัดความ Camera ใหม่ลงใน MapHelpers.swift ที่มองไปที่ใจกลางเมืองซานฟรานซิสโก

  public static var downtownSanFrancisco: Camera = .init(latitude: 37.7905, longitude: -122.3989, heading: 25, tilt: 71, range: 2500) 

เพิ่มไฟล์ใหม่ลงในโปรเจ็กต์ชื่อ ShapesDemo.swift เพิ่ม struct ชื่อ ShapesDemo ที่ใช้โปรโตคอล View และเพิ่ม body ลงใน struct

struct ShapesDemo: View {
  @State var camera: Camera = .downtownSanFrancisco

  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid) {

      }
    }
  }
}

คลาสที่คุณจะใช้จัดการข้อมูลรูปร่างคือ Polyline และ Polygon เปิด ShapesDemo.swift แล้วเพิ่มลงใน struct ดังนี้

var polyline: Polyline = .init(coordinates: [
    LatLngAltitude(latitude: 37.80515638571346, longitude: -122.4032569467164, altitude: 0),
    LatLngAltitude(latitude: 37.80337073509504, longitude: -122.4012878349353, altitude: 0),
    LatLngAltitude(latitude: 37.79925208843463, longitude: -122.3976697250461, altitude: 0),
    LatLngAltitude(latitude: 37.7989102378512, longitude: -122.3983408725656, altitude: 0),
    LatLngAltitude(latitude: 37.79887832784348, longitude: -122.3987094864192, altitude: 0),
    LatLngAltitude(latitude: 37.79786443410338, longitude: -122.4066878788802, altitude: 0),
    LatLngAltitude(latitude: 37.79549248916587, longitude: -122.4032992702785, altitude: 0),
    LatLngAltitude(latitude: 37.78861484290265, longitude: -122.4019489189814, altitude: 0),
    LatLngAltitude(latitude: 37.78618687561075, longitude: -122.398969592545, altitude: 0),
    LatLngAltitude(latitude: 37.7892310309145, longitude: -122.3951458683092, altitude: 0),
    LatLngAltitude(latitude: 37.7916358762409, longitude: -122.3981969390652, altitude: 0)
  ])
  .stroke(GoogleMaps3D.Polyline.StrokeStyle(
    strokeColor: UIColor(red: 0.09803921568627451, green: 0.403921568627451, blue: 0.8235294117647058, alpha: 1),
    strokeWidth: 10.0,
    outerColor: .white,
    outerWidth: 0.2
    ))
  .contour(GoogleMaps3D.Polyline.ContourStyle(isGeodesic: true))

  var originPolygon: Polygon = .init(outerCoordinates: [
    LatLngAltitude(latitude: 37.79165766856578, longitude:  -122.3983762901255, altitude: 300),
    LatLngAltitude(latitude: 37.7915324439261, longitude:  -122.3982171091383, altitude: 300),
    LatLngAltitude(latitude: 37.79166617650914, longitude:  -122.3980478493319, altitude: 300),
    LatLngAltitude(latitude: 37.79178986470217, longitude:  -122.3982041104199, altitude: 300),
    LatLngAltitude(latitude: 37.79165766856578, longitude:  -122.3983762901255, altitude: 300 )
  ],
  altitudeMode: .relativeToGround)
  .style(GoogleMaps3D.Polygon.StyleOptions(fillColor:.green, extruded: true) )

  var destinationPolygon: Polygon = .init(outerCoordinates: [
      LatLngAltitude(latitude: 37.80515661739527, longitude:  -122.4034307490334, altitude: 300),
      LatLngAltitude(latitude: 37.80503794515428, longitude:  -122.4032633416024, altitude: 300),
      LatLngAltitude(latitude: 37.80517850164195, longitude:  -122.4031056058006, altitude: 300),
      LatLngAltitude(latitude: 37.80529346901115, longitude:  -122.4032622466595, altitude: 300),
      LatLngAltitude(latitude: 37.80515661739527, longitude:  -122.4034307490334, altitude: 300 )
  ],
  altitudeMode: .relativeToGround)
  .style(GoogleMaps3D.Polygon.StyleOptions(fillColor:.red, extruded: true) )

สังเกตพารามิเตอร์การเริ่มต้นที่ใช้

  • altitudeMode: .relativeToGround ใช้เพื่อยื่นรูปหลายเหลี่ยมให้สูงขึ้นจากพื้นตามระดับที่ต้องการ
  • altitudeMode: .clampToGround ใช้เพื่อทำให้เส้นประกอบเป็นไปตามรูปร่างของพื้นผิวโลก
  • รูปแบบได้รับการตั้งค่าในออบเจ็กต์ Polygon โดยการต่อเชื่อมการเรียกใช้เมธอดกับ styleOptions() หลังจากเรียกใช้ .init()

เพิ่มรูปร่างลงในแผนที่

เช่นเดียวกับในขั้นตอนก่อนหน้า คุณสามารถเพิ่มรูปร่างลงในส่วนปิดท้ายของ Map ได้โดยตรง สร้าง Map ภายใน VStack

...
  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid) {
        polyline
        originPolygon
        destinationPolygon
      }
    }
  }
...

ดูตัวอย่างและเรียกใช้แอป

เพิ่มโค้ดเวอร์ชันตัวอย่างและตรวจสอบแอปในบานหน้าต่างแสดงตัวอย่างใน Xcode

#Preview {
  ShapesDemo()
}

หากต้องการเรียกใช้แอป ให้เพิ่ม NavigationLink ใหม่ลงใน GoogleMaps3DDemoApp.swift ซึ่งจะเปิดมุมมองเดโมใหม่

...
          NavigationLink(destination: ShapesDemo()) {
            Text("Shapes Demo")
          }
...

เรียกใช้แอปและสำรวจรูปร่างที่คุณเพิ่ม

9. จัดการเหตุการณ์การแตะเครื่องหมายสถานที่

ในขั้นตอนนี้ คุณจะได้เรียนรู้วิธีตอบสนองต่อการแตะเครื่องหมายตำแหน่งของผู้ใช้

แผนที่แสดงหน้าต่างป๊อปอัปที่มีรหัสสถานที่

หมายเหตุ: หากต้องการดูเครื่องหมายสถานที่บนแผนที่ คุณจะต้องตั้งค่า MapMode เป็น .hybrid

การจัดการการแตะต้องใช้เมธอด Map.onPlaceTap

เหตุการณ์ onPlaceTap มีออบเจ็กต์ PlaceTapInfo ที่คุณใช้รับรหัสสถานที่ของเครื่องหมายสถานที่ที่แตะได้

คุณสามารถใช้รหัสสถานที่เพื่อค้นหารายละเอียดเพิ่มเติมได้โดยใช้ Places SDK หรือ Places API

เพิ่ม Swift View ใหม่

เพิ่มโค้ดต่อไปนี้ลงในไฟล์ Swift ใหม่ชื่อ PlaceTapDemo.swift

import GoogleMaps3D
import SwiftUI

struct PlaceTapDemo: View {
  @State var camera: Camera = .sanFrancisco
  @State var isPresented = false
  @State var tapInfo: PlaceTapInfo?

  var body: some View {
    Map(camera: $camera, mode: .hybrid)
      .onPlaceTap { tapInfo in
        self.tapInfo = tapInfo
        isPresented.toggle()
      }
      .alert(
        "Place tapped - \(tapInfo?.placeId ?? "nil")",
        isPresented: $isPresented,
        actions: { Button("OK") {} }
      )
  }
}
#Preview {
  PlaceTapDemo()
}

ดูตัวอย่างและเรียกใช้แอป

เปิดช่องแสดงตัวอย่างเพื่อดูตัวอย่างแอป

หากต้องการเรียกใช้แอป ให้เพิ่ม NavigationLink ใหม่ลงใน GoogleMaps3DDemoApp.swift

...
          NavigationLink(destination: PlaceTapDemo()) {
            Text("Place Tap Demo")
          }
...

10. (ไม่บังคับ) ดำเนินการต่อ

ภาพเคลื่อนไหวขั้นสูงของกล้อง

Use Case บางรายการต้องใช้ภาพเคลื่อนไหวที่ราบรื่นตามลำดับหรือรายการตำแหน่งหรือสถานะของกล้อง เช่น โปรแกรมจำลองการบิน หรือการดูการเดินป่าหรือการวิ่งซ้ำ

ในขั้นตอนนี้ คุณจะได้ดูวิธีโหลดรายการสถานที่จากไฟล์ และแสดงภาพเคลื่อนไหวผ่านสถานที่แต่ละแห่งตามลำดับ

มุมมองแผนที่ 3 มิติของเส้นทางเข้าใกล้อินส์บรุค

โหลดไฟล์ที่มีลําดับสถานที่

ดาวน์โหลด flightpath.json จากที่เก็บแอปตัวอย่างของ GitHub

สร้างโฟลเดอร์ใหม่ในโปรเจ็กต์ Xcode ชื่อ JSON

ลาก flightpath.json ไปยังโฟลเดอร์ JSON ใน Xcode

กำหนดเป้าหมายให้เป็นผู้ชมเป้าหมายหลักของแอป ตรวจสอบว่าการตั้งค่าทรัพยากร App Bundle ของโปรเจ็กต์มีไฟล์นี้

สร้างไฟล์ Swift ใหม่ 2 ไฟล์ในแอปชื่อ FlightPathData.swift และ FlightDataLoader.swift

คัดลอกโค้ดต่อไปนี้ลงในแอป โค้ดนี้จะสร้างโครงสร้างและคลาสที่อ่านไฟล์ในเครื่องชื่อ "flighpath.json" และถอดรหัสเป็น JSON

โครงสร้าง FlightPathData และ FlightPathLocation จะแสดงโครงสร้างข้อมูลในไฟล์ JSON เป็นออบเจ็กต์ Swift

คลาส FlightDataLoader จะอ่านข้อมูลจากไฟล์และถอดรหัส โดยจะใช้โปรโตคอล ObservableObject เพื่อให้แอปของคุณสังเกตการเปลี่ยนแปลงของข้อมูลได้

ระบบจะแสดงข้อมูลที่แยกวิเคราะห์ผ่านพร็อพเพอร์ตี้ที่เผยแพร่

FlightPaths.swift

import GoogleMaps3D

struct FlightPathData: Decodable {
  let flight: [FlightPathLocation]
}

struct FlightPathLocation: Decodable {
  let timestamp: Int64
  let latitude: Double
  let longitude: Double
  let altitude: Double
  let bearing: Double
  let speed: Double
}

FlightDataLoader.swift

import Foundation

public class FlightDataLoader : ObservableObject {

  @Published var flightPathData: FlightPathData = FlightPathData(flight:[])
  @Published var isLoaded: Bool = false

  public init() {
    load("flightpath.json")
  }
  
  public func load(_ path: String) {
    if let url = Bundle.main.url(forResource: path, withExtension: nil){
      if let data = try? Data(contentsOf: url){
        let jsondecoder = JSONDecoder()
        do{
          let result = try jsondecoder.decode(FlightPathData.self, from: data)
          flightPathData = result
          isLoaded = true
        }
        catch {
          print("Error trying to load or parse the JSON file.")
        }
      }
    }
  }
}

สร้างภาพเคลื่อนไหวของกล้องไปตามแต่ละตำแหน่ง

หากต้องการทำให้กล้องเคลื่อนไหวระหว่างลำดับขั้นตอนต่างๆ คุณจะใช้ KeyframeAnimator

ระบบจะสร้าง Keyframe แต่ละรายการเป็น CubicKeyframe เพื่อให้การเปลี่ยนแปลงสถานะของกล้องแสดงเป็นภาพเคลื่อนไหวอย่างราบรื่น

การใช้ flyCameraTo() จะทำให้มุมมอง "สลับ" ระหว่างสถานที่แต่ละแห่ง

ก่อนอื่นให้ประกาศกล้องชื่อ "innsbruck" ใน MapHelpers.swift

public static var innsbruck: Camera = .init(
  latitude: 47.263,
  longitude: 11.3704,
  altitude: 640.08,
  heading: 237,
  tilt: 80.0,
  roll: 0.0,
  range: 200)

ตอนนี้ให้ตั้งค่ามุมมองใหม่ในไฟล์ใหม่ชื่อ FlyAlongRoute.swift

นําเข้า SwiftUI และ GoogleMaps3D เพิ่ม Map และ Button ไว้ใน VStack ตั้งค่า Button เพื่อสลับสถานะของตัวแปรบูลีน animation

ประกาศออบเจ็กต์ State สำหรับ FlightDataLoader ซึ่งจะโหลดไฟล์ JSON เมื่อเริ่มต้น

import GoogleMaps3D
import SwiftUI

struct FlyAlongRoute: View {
  @State private var camera: Camera = .innsbruck
  @State private var flyToDuration: TimeInterval = 5
  @State var animation: Bool = true

  @StateObject var flightData: FlightDataLoader = FlightDataLoader()

  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid)
      Button("Fly Along Route"){
        animation.toggle()
      }
    }
  }
}

สร้างคีย์เฟรม

ขั้นตอนพื้นฐานคือการสร้างฟังก์ชันที่แสดงผลเฟรมใหม่ในลำดับภาพเคลื่อนไหว เฟรมใหม่แต่ละเฟรมจะกำหนดสถานะกล้องถัดไปเพื่อให้นักสร้างภาพเคลื่อนไหวใช้สร้างภาพเคลื่อนไหว เมื่อสร้างฟังก์ชันนี้แล้ว ให้เรียกใช้ฟังก์ชันนั้นกับสถานที่แต่ละแห่งจากไฟล์ตามลำดับ

เพิ่มฟังก์ชัน 2 รายการลงในโครงสร้าง FlyAlongRoute ฟังก์ชัน makeKeyFrame จะแสดงผล CubicKeyframe ที่มีสถานะกล้อง ฟังก์ชัน makeCamera จะนําขั้นตอนในลําดับข้อมูลเที่ยวบินไปแสดงผลออบเจ็กต์ Camera ที่แสดงขั้นตอนนั้น

func makeKeyFrame(step: FlightPathLocation) -> CubicKeyframe<Camera> {
  return CubicKeyframe(
    makeCamera(step: step),
    duration: flyToDuration
  )
}

func makeCamera(step: FlightPathLocation) -> Camera {
  return .init(
    latitude: step.latitude,
    longitude: step.longitude,
    altitude: step.altitude,
    heading: step.bearing,
    tilt: 75,
    roll: 0,
    range: 200
  )
}

ใส่ภาพเคลื่อนไหวเข้าด้วยกัน

เรียกใช้ keyframeAnimator หลังจากการเริ่มต้น Map และตั้งค่าเริ่มต้น

คุณจะต้องมีสถานะกล้องเริ่มต้นตามตำแหน่งแรกในเส้นทางการบิน

ภาพเคลื่อนไหวควรทริกเกอร์ตามสถานะที่เปลี่ยนแปลงของตัวแปร

เนื้อหา keyframeAnimator ควรเป็นแผนที่

รายการคีย์เฟรมจริงสร้างขึ้นโดยการวนดูแต่ละตำแหน่งในเส้นทางการบิน

   VStack {
      Map(camera: $camera, mode: .hybrid)
        .keyframeAnimator(
          initialValue: makeCamera(step: flightData.flightPathData.flight[0]),
          trigger: animation,
          content: { view, value in
            Map(camera: .constant(value), mode: .hybrid)
          },
          keyframes: { _ in
            KeyframeTrack(content: {
              for i in  1...flightData.flightPathData.flight.count-1 {
                makeKeyFrame(step: flightData.flightPathData.flight[i])
              }
            })
          }
        )
   }

ดูตัวอย่างและเรียกใช้แอป

เปิดบานหน้าต่างแสดงตัวอย่างเพื่อดูตัวอย่างมุมมอง

เพิ่ม NavigationLink ใหม่ที่มีปลายทางเป็น FlightPathDemo() ไปยัง GoogleMaps3DDemoApp.swift แล้วเรียกใช้แอปเพื่อลองใช้

11. ขอแสดงความยินดี

คุณสร้างแอปพลิเคชันที่มีคุณสมบัติต่อไปนี้เรียบร้อยแล้ว

  • เพิ่มแผนที่ 3 มิติพื้นฐานลงในแอป
  • เพิ่มเครื่องหมาย เส้น รูปหลายเหลี่ยม และโมเดลลงในแผนที่
  • ใช้โค้ดเพื่อควบคุมกล้องให้บินผ่านแผนที่และรอบๆ สถานที่ที่เฉพาะเจาะจง

สิ่งที่คุณได้เรียนรู้

  • วิธีเพิ่มแพ็กเกจ GoogleMaps3D ลงในแอป SwiftUI ของ Xcode
  • วิธีเริ่มต้นใช้งานแผนที่ 3 มิติด้วยคีย์ API และมุมมองเริ่มต้น
  • วิธีเพิ่มเครื่องหมาย โมเดล 3 มิติ เส้น และรูปหลายเหลี่ยมลงในแผนที่
  • วิธีควบคุมกล้องเพื่อแสดงภาพเคลื่อนไหวไปยังตำแหน่งอื่น
  • วิธีจัดการเหตุการณ์การคลิกบนเครื่องหมายสถานที่

ขั้นตอนถัดไปคือ

  • ดูรายละเอียดเพิ่มเติมเกี่ยวกับสิ่งที่คุณทำได้ด้วย Maps 3D SDK สำหรับ iOS ได้ในคู่มือนักพัฒนาซอฟต์แวร์
  • ช่วยเราสร้างเนื้อหาที่คุณจะพบว่ามีประโยชน์มากที่สุดโดยตอบแบบสํารวจต่อไปนี้

คุณต้องการดู Codelab อื่นๆ ใดอีก

การแสดงข้อมูลเป็นภาพบนแผนที่ ข้อมูลเพิ่มเติมเกี่ยวกับการปรับแต่งสไตล์แผนที่ การสร้างเพื่อการโต้ตอบแบบ 3 มิติในแผนที่