new change to use intaleq_map sdk v2 04-17-1

This commit is contained in:
Hamza-Ayed
2026-04-17 14:47:11 +03:00
parent 834c220d31
commit cc62019609
16 changed files with 1436 additions and 357 deletions
@@ -0,0 +1,97 @@
import 'dart:typed_data' show Uint8List;
import 'dart:ui' show Offset;
/// Defines a bitmap image for use as a [Marker] icon.
///
/// This class mirrors `BitmapDescriptor` from `google_maps_flutter`.
/// Internally it resolves to a MapLibre image name or inline bytes.
class InlqBitmap {
const InlqBitmap._({
required this.mapLibreImageId,
this.size,
this.offset,
this.bytes,
this.assetName,
});
/// The image name used in MapLibre style expressions.
final String mapLibreImageId;
/// Optional size multiplier (MapLibre iconSize).
final double? size;
/// Optional pixel offset from the anchor point.
final Offset? offset;
/// Raw PNG/JPEG bytes (used with [fromBytes]).
final Uint8List? bytes;
/// Flutter asset path (used with [fromAsset]).
final String? assetName;
// ── Built-in defaults ──────────────────────────────────────
/// The standard blue pin marker (default).
static const InlqBitmap defaultMarker = InlqBitmap._(
mapLibreImageId: 'intaleq-marker-default',
);
// Hue constants matching google_maps_flutter
static const double hueRed = 0.0;
static const double hueOrange = 30.0;
static const double hueYellow = 60.0;
static const double hueGreen = 120.0;
static const double hueCyan = 180.0;
static const double hueAzure = 210.0;
static const double hueBlue = 240.0;
static const double hueViolet = 270.0;
static const double hueMagenta = 300.0;
static const double hueRose = 330.0;
/// A colored pin marker. [hue] is a value from 0–360 (HSV hue).
///
/// Use the `hue*` constants (e.g. [InlqBitmap.hueBlue]) for clarity.
static InlqBitmap defaultMarkerWithHue(double hue) {
return InlqBitmap._(
mapLibreImageId: 'intaleq-marker-hue-${hue.toInt()}',
size: 1.0,
);
}
// ── Custom bitmaps ─────────────────────────────────────────
/// Load an icon from a Flutter asset bundle.
///
/// ```dart
/// icon: InlqBitmap.fromAsset('assets/icons/car.png')
/// ```
static InlqBitmap fromAsset(String assetName, {double? size}) {
return InlqBitmap._(
mapLibreImageId: 'asset:$assetName',
assetName: assetName,
size: size,
);
}
/// Load an icon from raw image bytes (e.g. a network image you decoded).
static InlqBitmap fromBytes(Uint8List bytes, {double? size}) {
return InlqBitmap._(
mapLibreImageId: 'bytes:${bytes.hashCode}',
bytes: bytes,
size: size,
);
}
/// Reference an image already registered in the MapLibre style.
///
/// Use this when your map style JSON already defines the sprite image.
static InlqBitmap fromStyleImage(String imageId, {double? size}) {
return InlqBitmap._(
mapLibreImageId: imageId,
size: size,
);
}
@override
String toString() => 'InlqBitmap($mapLibreImageId)';
}
+414 -82
View File
@@ -1,110 +1,442 @@
import 'dart:ui';
import 'package:maplibre_gl/maplibre_gl.dart' as maplibre;
import 'dart:ui' show Offset, Color;
import 'package:flutter/foundation.dart' show VoidCallback, ValueChanged;
import 'package:maplibre_gl/maplibre_gl.dart' as mgl;
import '../constants/colors.dart';
import 'bitmap.dart';
import 'info_window.dart';
class MarkerOptions {
final maplibre.LatLng position;
final String? iconImage;
final double? iconSize;
final double? iconRotate;
final String? textField;
final double? textOffset;
// ─────────────────────────────────────────────────────────────
// ID types (identical contract to google_maps_flutter)
// ─────────────────────────────────────────────────────────────
MarkerOptions({
required this.position,
this.iconImage,
this.iconSize,
this.iconRotate,
this.textField,
this.textOffset,
});
maplibre.SymbolOptions toSymbolOptions() {
return maplibre.SymbolOptions(
geometry: position,
iconImage: iconImage,
iconSize: iconSize,
iconRotate: iconRotate,
textField: textField,
textOffset: textOffset != null ? Offset(0, textOffset!) : null,
);
}
class MarkerId {
const MarkerId(this.value);
final String value;
@override
bool operator ==(Object o) => o is MarkerId && o.value == value;
@override
int get hashCode => value.hashCode;
@override
String toString() => 'MarkerId($value)';
}
class PolylineOptions {
final List<maplibre.LatLng> coordinates;
final String? color;
final double? width;
final double? opacity;
class PolylineId {
const PolylineId(this.value);
final String value;
@override
bool operator ==(Object o) => o is PolylineId && o.value == value;
@override
int get hashCode => value.hashCode;
@override
String toString() => 'PolylineId($value)';
}
PolylineOptions({
required this.coordinates,
this.color,
this.width,
this.opacity,
class CircleId {
const CircleId(this.value);
final String value;
@override
bool operator ==(Object o) => o is CircleId && o.value == value;
@override
int get hashCode => value.hashCode;
@override
String toString() => 'CircleId($value)';
}
class PolygonId {
const PolygonId(this.value);
final String value;
@override
bool operator ==(Object o) => o is PolygonId && o.value == value;
@override
int get hashCode => value.hashCode;
@override
String toString() => 'PolygonId($value)';
}
// ─────────────────────────────────────────────────────────────
// Marker (mirrors google_maps_flutter Marker)
// ─────────────────────────────────────────────────────────────
class Marker {
const Marker({
required this.markerId,
required this.position,
this.alpha = 1.0,
this.anchor = const Offset(0.5, 1.0),
this.draggable = false,
this.flat = false,
this.icon = InlqBitmap.defaultMarker,
this.infoWindow = InfoWindow.noText,
this.rotation = 0.0,
this.visible = true,
this.zIndex = 0.0,
this.onTap,
this.onDragStart,
this.onDrag,
this.onDragEnd,
});
maplibre.LineOptions toLineOptions() {
return maplibre.LineOptions(
geometry: coordinates,
lineColor: color ?? IntaleqColors.primaryBlue,
lineWidth: width ?? 5.0,
lineOpacity: opacity ?? 1.0,
final MarkerId markerId;
final mgl.LatLng position;
/// Opacity of the marker icon, from 0.0 (transparent) to 1.0 (opaque).
final double alpha;
/// Icon anchor point as a fraction of icon size. (0.5, 1.0) = bottom-center.
final Offset anchor;
/// Whether the marker can be dragged by the user.
final bool draggable;
/// When true the marker is drawn flat against the map surface.
final bool flat;
/// The icon image for the marker.
final InlqBitmap icon;
/// Info window shown when the marker is tapped.
final InfoWindow infoWindow;
/// Rotation of the marker in degrees clockwise from north.
final double rotation;
/// Whether the marker is visible on the map.
final bool visible;
/// Z-index for draw ordering among markers.
final double zIndex;
/// Called when the marker is tapped.
final VoidCallback? onTap;
/// Called when a drag starts.
final ValueChanged<mgl.LatLng>? onDragStart;
/// Called repeatedly during drag.
final ValueChanged<mgl.LatLng>? onDrag;
/// Called when drag ends.
final ValueChanged<mgl.LatLng>? onDragEnd;
Marker copyWith({
mgl.LatLng? position,
double? alpha,
Offset? anchor,
bool? draggable,
bool? flat,
InlqBitmap? icon,
InfoWindow? infoWindow,
double? rotation,
bool? visible,
double? zIndex,
VoidCallback? onTap,
ValueChanged<mgl.LatLng>? onDragStart,
ValueChanged<mgl.LatLng>? onDrag,
ValueChanged<mgl.LatLng>? onDragEnd,
}) {
return Marker(
markerId: markerId,
position: position ?? this.position,
alpha: alpha ?? this.alpha,
anchor: anchor ?? this.anchor,
draggable: draggable ?? this.draggable,
flat: flat ?? this.flat,
icon: icon ?? this.icon,
infoWindow: infoWindow ?? this.infoWindow,
rotation: rotation ?? this.rotation,
visible: visible ?? this.visible,
zIndex: zIndex ?? this.zIndex,
onTap: onTap ?? this.onTap,
onDragStart: onDragStart ?? this.onDragStart,
onDrag: onDrag ?? this.onDrag,
onDragEnd: onDragEnd ?? this.onDragEnd,
);
}
/// Internal: converts to MapLibre SymbolOptions.
mgl.SymbolOptions toSymbolOptions() {
return mgl.SymbolOptions(
geometry: position,
iconImage: icon.mapLibreImageId,
iconSize: icon.size,
iconRotate: rotation,
iconOpacity: alpha,
iconAnchor: _anchorToString(anchor),
draggable: draggable,
zIndex: zIndex.toInt(),
textField: infoWindow.title,
iconOffset: icon.offset,
);
}
static String _anchorToString(Offset anchor) {
if (anchor.dx == 0.5 && anchor.dy == 1.0) return 'bottom';
if (anchor.dx == 0.5 && anchor.dy == 0.0) return 'top';
if (anchor.dx == 0.0 && anchor.dy == 0.5) return 'left';
if (anchor.dx == 1.0 && anchor.dy == 0.5) return 'right';
if (anchor.dx == 0.5 && anchor.dy == 0.5) return 'center';
return 'bottom';
}
@override
bool operator ==(Object o) => o is Marker && o.markerId == markerId;
@override
int get hashCode => markerId.hashCode;
}
// ─────────────────────────────────────────────────────────────
// Polyline (mirrors google_maps_flutter Polyline)
// ─────────────────────────────────────────────────────────────
class Polyline {
const Polyline({
required this.polylineId,
required this.points,
this.color = const Color(0xFF0D47A1),
this.width = 5,
this.visible = true,
this.zIndex = 0,
this.geodesic = false,
this.onTap,
});
final PolylineId polylineId;
/// The ordered list of points that make up the polyline.
final List<mgl.LatLng> points;
/// Line stroke color.
final Color color;
/// Line stroke width in screen pixels.
final int width;
/// Whether the polyline is visible on the map.
final bool visible;
final int zIndex;
/// When true, the polyline follows the curvature of the Earth.
final bool geodesic;
/// Called when the polyline is tapped.
final VoidCallback? onTap;
Polyline copyWith({
List<mgl.LatLng>? points,
Color? color,
int? width,
bool? visible,
int? zIndex,
bool? geodesic,
VoidCallback? onTap,
}) {
return Polyline(
polylineId: polylineId,
points: points ?? this.points,
color: color ?? this.color,
width: width ?? this.width,
visible: visible ?? this.visible,
zIndex: zIndex ?? this.zIndex,
geodesic: geodesic ?? this.geodesic,
onTap: onTap ?? this.onTap,
);
}
/// Internal: converts to MapLibre LineOptions.
mgl.LineOptions toLineOptions() {
return mgl.LineOptions(
geometry: points,
lineColor: _colorToHex(color),
lineWidth: width.toDouble(),
lineOpacity: visible ? color.opacity : 0.0,
lineJoin: 'round',
);
}
@override
bool operator ==(Object o) => o is Polyline && o.polylineId == polylineId;
@override
int get hashCode => polylineId.hashCode;
}
class CircleOptions {
final maplibre.LatLng position;
final double radius;
final String? color;
final double? opacity;
final String? strokeColor;
final double? strokeWidth;
// ─────────────────────────────────────────────────────────────
// Circle (mirrors google_maps_flutter Circle)
// ─────────────────────────────────────────────────────────────
CircleOptions({
required this.position,
class Circle {
const Circle({
required this.circleId,
required this.center,
required this.radius,
this.color,
this.opacity,
this.strokeColor,
this.strokeWidth,
this.fillColor = const Color(0x1A0D47A1),
this.strokeColor = const Color(0xFF0D47A1),
this.strokeWidth = 2,
this.visible = true,
this.zIndex = 0,
this.onTap,
});
maplibre.CircleOptions toMapLibreOptions() {
return maplibre.CircleOptions(
geometry: position,
circleRadius: radius,
circleColor: color ?? IntaleqColors.primaryBlue,
circleOpacity: opacity ?? 0.6,
circleStrokeColor: strokeColor,
circleStrokeWidth: strokeWidth,
final CircleId circleId;
/// Center of the circle.
final mgl.LatLng center;
/// Radius in meters.
final double radius;
/// Fill color (with alpha for opacity).
final Color fillColor;
/// Stroke color.
final Color strokeColor;
/// Stroke width in pixels.
final int strokeWidth;
final bool visible;
final int zIndex;
/// Called when the circle is tapped.
final VoidCallback? onTap;
Circle copyWith({
mgl.LatLng? center,
double? radius,
Color? fillColor,
Color? strokeColor,
int? strokeWidth,
bool? visible,
int? zIndex,
VoidCallback? onTap,
}) {
return Circle(
circleId: circleId,
center: center ?? this.center,
radius: radius ?? this.radius,
fillColor: fillColor ?? this.fillColor,
strokeColor: strokeColor ?? this.strokeColor,
strokeWidth: strokeWidth ?? this.strokeWidth,
visible: visible ?? this.visible,
zIndex: zIndex ?? this.zIndex,
onTap: onTap ?? this.onTap,
);
}
/// Internal: converts to MapLibre CircleOptions.
/// Note: MapLibre circle radius is in pixels, not meters. For a true
/// meter-accurate circle, use [IntaleqMapController.addCircleAccurate].
mgl.CircleOptions toCircleOptions() {
return mgl.CircleOptions(
geometry: center,
circleRadius:
radius / 10, // approximate; use addCircleAccurate for precision
circleColor: _colorToHex(fillColor),
circleOpacity: visible ? fillColor.opacity : 0.0,
circleStrokeColor: _colorToHex(strokeColor),
circleStrokeWidth: strokeWidth.toDouble(),
circleStrokeOpacity: visible ? strokeColor.opacity : 0.0,
);
}
@override
bool operator ==(Object o) => o is Circle && o.circleId == circleId;
@override
int get hashCode => circleId.hashCode;
}
class PolygonOptions {
/// The list of rings. The first ring is the outer boundary,
/// and subsequent rings are holes.
final List<List<maplibre.LatLng>> rings;
final String? fillColor;
final double? fillOpacity;
final String? outlineColor;
// ─────────────────────────────────────────────────────────────
// Polygon (mirrors google_maps_flutter Polygon)
// ─────────────────────────────────────────────────────────────
PolygonOptions({
required this.rings,
this.fillColor,
this.fillOpacity,
this.outlineColor,
class Polygon {
const Polygon({
required this.polygonId,
required this.points,
this.holes = const [],
this.fillColor = const Color(0x1ABDBDBD),
this.strokeColor = const Color(0xFF0D47A1),
this.strokeWidth = 2,
this.visible = true,
this.zIndex = 0,
this.geodesic = false,
this.onTap,
});
maplibre.FillOptions toFillOptions() {
return maplibre.FillOptions(
final PolygonId polygonId;
/// Outer boundary of the polygon.
final List<mgl.LatLng> points;
/// Holes inside the polygon (each is a list of LatLng).
final List<List<mgl.LatLng>> holes;
/// Fill color.
final Color fillColor;
/// Outline stroke color.
final Color strokeColor;
/// Outline stroke width in pixels.
final int strokeWidth;
final bool visible;
final int zIndex;
final bool geodesic;
/// Called when the polygon is tapped.
final VoidCallback? onTap;
Polygon copyWith({
List<mgl.LatLng>? points,
List<List<mgl.LatLng>>? holes,
Color? fillColor,
Color? strokeColor,
int? strokeWidth,
bool? visible,
int? zIndex,
bool? geodesic,
VoidCallback? onTap,
}) {
return Polygon(
polygonId: polygonId,
points: points ?? this.points,
holes: holes ?? this.holes,
fillColor: fillColor ?? this.fillColor,
strokeColor: strokeColor ?? this.strokeColor,
strokeWidth: strokeWidth ?? this.strokeWidth,
visible: visible ?? this.visible,
zIndex: zIndex ?? this.zIndex,
geodesic: geodesic ?? this.geodesic,
onTap: onTap ?? this.onTap,
);
}
/// Internal: converts to MapLibre FillOptions.
/// The first ring is the outer boundary; subsequent rings are holes.
mgl.FillOptions toFillOptions() {
final rings = [points, ...holes];
return mgl.FillOptions(
geometry: rings,
fillColor: fillColor ?? IntaleqColors.fillGray,
fillOpacity: fillOpacity ?? 0.4,
fillOutlineColor: outlineColor ?? IntaleqColors.primaryBlue,
fillColor: _colorToHex(fillColor),
fillOpacity: visible ? fillColor.opacity : 0.0,
fillOutlineColor: _colorToHex(strokeColor),
);
}
@override
bool operator ==(Object o) => o is Polygon && o.polygonId == polygonId;
@override
int get hashCode => polygonId.hashCode;
}
// ─────────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────────
/// Converts a Flutter [Color] to a CSS hex string (e.g. '#0D47A1').
String _colorToHex(Color color) {
return '#${color.red.toRadixString(16).padLeft(2, '0')}'
'${color.green.toRadixString(16).padLeft(2, '0')}'
'${color.blue.toRadixString(16).padLeft(2, '0')}';
}
@@ -0,0 +1,39 @@
import 'package:flutter/foundation.dart' show VoidCallback;
/// Text labels and a tap callback shown when a [Marker] is tapped.
///
/// Mirrors `InfoWindow` from `google_maps_flutter`.
class InfoWindow {
const InfoWindow({
this.title,
this.snippet,
this.onTap,
});
/// Displayed as the window heading.
final String? title;
/// Displayed below the title as secondary text.
final String? snippet;
/// Called when the info window itself is tapped.
final VoidCallback? onTap;
/// An [InfoWindow] with no text (the default).
static const InfoWindow noText = InfoWindow();
InfoWindow copyWith({
String? title,
String? snippet,
VoidCallback? onTap,
}) {
return InfoWindow(
title: title ?? this.title,
snippet: snippet ?? this.snippet,
onTap: onTap ?? this.onTap,
);
}
@override
String toString() => 'InfoWindow(title: $title, snippet: $snippet)';
}
@@ -0,0 +1,66 @@
import 'package:maplibre_gl/maplibre_gl.dart'
show LatLngBounds, LatLng, CameraPosition;
import '../intaleq_map_controller.dart';
// ─────────────────────────────────────────────────────────────
// IntaleqMapType (mirrors MapType in google_maps_flutter)
// ─────────────────────────────────────────────────────────────
/// The visual style of the base map tiles.
enum IntaleqMapType {
/// No base map tiles.
none,
/// Dark premium Obsidian style — the Intaleq default.
normal,
/// High-contrast light style.
light,
/// Satellite imagery with road labels overlaid.
satellite,
}
// ─────────────────────────────────────────────────────────────
// MinMaxZoomPreference (identical to google_maps_flutter)
// ─────────────────────────────────────────────────────────────
/// Preferred zoom level constraints for the camera.
class MinMaxZoomPreference {
const MinMaxZoomPreference(this.minZoom, this.maxZoom);
final double? minZoom;
final double? maxZoom;
/// Unbounded zoom (default).
static const MinMaxZoomPreference unbounded =
MinMaxZoomPreference(null, null);
@override
String toString() => 'MinMaxZoomPreference($minZoom, $maxZoom)';
}
// ─────────────────────────────────────────────────────────────
// CameraTargetBounds (identical to google_maps_flutter)
// ─────────────────────────────────────────────────────────────
/// Constrains the camera target to a [LatLngBounds] region.
class CameraTargetBounds {
const CameraTargetBounds(this.bounds);
final LatLngBounds? bounds;
/// Unbounded (default).
static const CameraTargetBounds unbounded = CameraTargetBounds(null);
@override
String toString() => 'CameraTargetBounds($bounds)';
}
// ─────────────────────────────────────────────────────────────
// Typedefs (same names as google_maps_flutter)
// ─────────────────────────────────────────────────────────────
typedef MapCreatedCallback = void Function(IntaleqMapController controller);
typedef ArgumentCallback<T> = void Function(T argument);
typedef CameraPositionCallback = void Function(CameraPosition position);