Kamera
Tekan "Buka Kamera" untuk memulai
πΊοΈ
Foto akan menyertakan watermark: OmahsinauMap Β· nama Β· koordinat Β· peta mini
Lokasi GPS
Latitude
β menunggu β
Longitude
β menunggu β
Alamat (Reverse Geocoding)
Belum ada data lokasi
Peta Lokasi
πΊοΈ
Peta akan muncul setelah lokasi didapat
Menggunakan OpenStreetMap / Leaflet.js
Galeri Foto
π
Belum ada foto tersimpan
Ambil foto dari tab Kamera
Kode Kotlin Android
MainActivity
CameraX
GPS/Location
Watermark
GoogleMaps
build.gradle
// MainActivity.kt package com.example.omahsinaumap import android.Manifest import android.content.pm.PackageManager import android.os.Bundle import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import com.example.geomap.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private lateinit var cameraManager: CameraManager private lateinit var locationManager: LocationManager private lateinit var mapsManager: MapsManager private val permissions = arrayOf( Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.WRITE_EXTERNAL_STORAGE ) private val permLauncher = registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { results -> val allGranted = results.values.all { it } if (allGranted) initFeatures() else showPermissionDeniedDialog() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) checkAndRequestPermissions() } private fun checkAndRequestPermissions() { val denied = permissions.filter { ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED } if (denied.isEmpty()) initFeatures() else permLauncher.launch(denied.toTypedArray()) } private fun initFeatures() { cameraManager = CameraManager(this, binding) locationManager = LocationManager(this) { location -> binding.tvLat.text = location.latitude.toString() binding.tvLng.text = location.longitude.toString() mapsManager.updateMarker(location) locationManager.reverseGeocode(location) { address -> binding.tvAddress.text = address } } mapsManager = MapsManager(binding.mapView) locationManager.startTracking() } }
// CameraManager.kt β CameraX + Watermark class CameraManager( private val activity: AppCompatActivity, private val binding: ActivityMainBinding ) { private var imageCapture: ImageCapture? = null private var cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA fun startCamera() { val cameraProviderFuture = ProcessCameraProvider.getInstance(activity) cameraProviderFuture.addListener({ val cameraProvider = cameraProviderFuture.get() val preview = Preview.Builder().build().also { it.setSurfaceProvider(binding.previewView.surfaceProvider) } imageCapture = ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .build() try { cameraProvider.unbindAll() cameraProvider.bindToLifecycle( activity, cameraSelector, preview, imageCapture ) } catch (e: Exception) { Log.e("Camera", "Bind failed", e) } }, ContextCompat.getMainExecutor(activity)) } fun capturePhoto( location: Location?, address: String, identity: String // NEW: identity name ) { val outputFile = createOutputFile() val outputOptions = ImageCapture.OutputFileOptions.Builder(outputFile).build() imageCapture?.takePicture( outputOptions, ContextCompat.getMainExecutor(activity), object : ImageCapture.OnImageSavedCallback { override fun onImageSaved( output: ImageCapture.OutputFileResults ) { WatermarkHelper.addWatermark( outputFile, location, address, identity ) } override fun onError(e: ImageCaptureException) { Log.e("Camera", "Capture error", e) } } ) } }
// LocationManager.kt β FusedLocationProvider + Geocoder class LocationManager( private val context: Context, private val onUpdate: (Location) -> Unit ) { private val fusedClient = LocationServices.getFusedLocationProviderClient(context) private val locationRequest = LocationRequest.Builder( Priority.PRIORITY_HIGH_ACCURACY, 5000L ).setMinUpdateIntervalMillis(2000L).build() private val locationCallback = object : LocationCallback() { override fun onLocationResult(result: LocationResult) { result.lastLocation?.let { onUpdate(it) } } } @SuppressLint("MissingPermission") fun startTracking() { fusedClient.requestLocationUpdates( locationRequest, locationCallback, Looper.getMainLooper() ) } fun reverseGeocode(location: Location, onResult: (String) -> Unit) { CoroutineScope(Dispatchers.IO).launch { try { val geocoder = Geocoder(context, Locale.getDefault()) val address = geocoder.getFromLocation( location.latitude, location.longitude, 1 )?.firstOrNull()?.getAddressLine(0) ?: "Tidak ditemukan" withContext(Dispatchers.Main) { onResult(address) } } catch (e: IOException) { withContext(Dispatchers.Main) { onResult("Gagal: ${e.message}") } } } } }
// WatermarkHelper.kt β Termasuk peta mini + identitas + OmahsinauMap object WatermarkHelper { fun addWatermark( file: File, location: Location?, address: String, identity: String // identitas pengguna ) { val original = BitmapFactory.decodeFile(file.path) val mutable = original.copy(Bitmap.Config.ARGB_8888, true) val canvas = Canvas(mutable) val W = mutable.width.toFloat() val H = mutable.height.toFloat() val stripH = H * 0.22f // ββ Semi-transparent background ββ Paint().apply { color = Color.argb(200,8,8,18) }.also { canvas.drawRect(0f, H - stripH, W, H, it) } // ββ GeoMap logo area (left column) ββ val logoSize = stripH * 0.38f val textPaint = Paint().apply { isAntiAlias = true; typeface = Typeface.MONOSPACE color = Color.WHITE; setShadowLayer(3f,1f,1f,Color.BLACK) } // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // WATERMARK β OmahsinauMap logo Β· name Β· info Β· mini-map (Kotlin) textPaint.textSize = logoSize; textPaint.color = Color.parseColor("#00e5ff") canvas.drawText("OmahsinauMap", W*0.02f, H - stripH + logoSize + W*0.005f, textPaint) // Divider line Paint().apply { color = Color.parseColor("#00e5ff"); strokeWidth = 2f; alpha = 120 }.also { canvas.drawLine(W*0.02f, H - stripH + logoSize*1.3f, W*0.28f, H - stripH + logoSize*1.3f, it) } // Identity, timestamp, address, coords textPaint.textSize = logoSize * 0.55f; textPaint.color = Color.WHITE val lineH = textPaint.textSize * 1.5f val x = W * 0.02f var y = H - stripH + logoSize * 1.3f + textPaint.textSize + 8f if (identity.isNotBlank()) { textPaint.color = Color.parseColor("#f59e0b") canvas.drawText("π€ $identity", x, y, textPaint); y += lineH textPaint.color = Color.WHITE } val ts = SimpleDateFormat("dd-MM-yyyy HH:mm", Locale.getDefault()).format(Date()) canvas.drawText("π $ts", x, y, textPaint); y += lineH canvas.drawText("π ${address.take(38)}", x, y, textPaint); y += lineH val coord = location?.let { "π° %.6f, %.6f".format(it.latitude, it.longitude) } ?: "π° Tidak tersedia" canvas.drawText(coord, x, y, textPaint) // ββ Mini-map (right column) ββ MapThumbnailHelper.draw(canvas, location, W, H, stripH) // Save FileOutputStream(file).use { mutable.compress(Bitmap.CompressFormat.JPEG, 95, it) } original.recycle(); mutable.recycle() } } // MapThumbnailHelper.kt β draws a stylized mini-map on Canvas object MapThumbnailHelper { fun draw(canvas: Canvas, loc: Location?, W: Float, H: Float, stripH: Float) { val mapW = W * 0.28f val mapH = stripH * 0.88f val mapX = W - mapW - W * 0.02f val mapY = H - stripH + (stripH - mapH) / 2f // Background + border drawn, grid + pin drawn inside // (full implementation in repo) } }
// MapsManager.kt β Google Maps Marker class MapsManager( private val mapFragment: SupportMapFragment ) : OnMapReadyCallback { private var googleMap: GoogleMap? = null private var currentMarker: Marker? = null init { mapFragment.getMapAsync(this) } override fun onMapReady(map: GoogleMap) { googleMap = map googleMap?.apply { uiSettings.isZoomControlsEnabled = true mapType = GoogleMap.MAP_TYPE_NORMAL } } fun updateMarker(location: Location) { val latLng = LatLng(location.latitude, location.longitude) googleMap?.apply { currentMarker?.remove() currentMarker = addMarker( MarkerOptions().position(latLng).title("Lokasi Anda") .icon(BitmapDescriptorFactory.defaultMarker( BitmapDescriptorFactory.HUE_CYAN)) ) animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 16f)) } } }
// build.gradle (app level) android { compileSdk 34 defaultConfig { minSdk 24; targetSdk 34 } buildFeatures { viewBinding = true } } dependencies { implementation "androidx.camera:camera-core:1.3.1" implementation "androidx.camera:camera-camera2:1.3.1" implementation "androidx.camera:camera-lifecycle:1.3.1" implementation "androidx.camera:camera-view:1.3.1" implementation "com.google.android.gms:play-services-location:21.2.0" implementation "com.google.android.gms:play-services-maps:18.2.0" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0" implementation "androidx.appcompat:appcompat:1.6.1" implementation "com.google.android.material:material:1.11.0" }