smap_flutter 1.1.5
smap_flutter: ^1.1.5 copied to clipboard
sMap for Flutter: maps from the sMap service in Flutter apps, with styles, labels in any language, clustered points, lines, areas and 3D buildings.
example/lib/main.dart
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:smap_flutter/smap_flutter.dart';
/// Pass a token at build time: `flutter run --dart-define=SMAP_TOKEN=sm1....`
/// (an app normally gets it from its own server through `getToken`).
const String token = String.fromEnvironment('SMAP_TOKEN');
/// Or a map key whose allowlist names this app: `--dart-define=SMAP_API_KEY=smk_...`.
const String apiKey = String.fromEnvironment('SMAP_API_KEY');
const String baseUrl = String.fromEnvironment('SMAP_BASE_URL', defaultValue: 'https://tile.srsr.ca');
/// Where the example keeps the hidden map tools (the SDK keeps no state itself).
const String toolsKey = 'smap-example-hidden-tools';
late final SharedPreferences prefs;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
prefs = await SharedPreferences.getInstance();
runApp(const DemoApp());
}
class DemoApp extends StatelessWidget {
const DemoApp({super.key});
@override
Widget build(BuildContext context) => MaterialApp(
title: 'sMap demo',
theme: ThemeData(colorSchemeSeed: const Color(0xFF2563EB), useMaterial3: true),
home: const DemoScreen(),
);
}
class DemoScreen extends StatefulWidget {
const DemoScreen({super.key});
@override
State<DemoScreen> createState() => _DemoScreenState();
}
class _DemoScreenState extends State<DemoScreen> {
static const center = LatLng(43.6532, -79.3832);
static const languages = ['en', 'fr', 'de', 'es', 'zh-Hans', 'ja'];
SMapController? _map;
SMapStyle _style = SMapStyle.light;
String _language = 'en';
bool _buildings = false;
/// 2,000 points scattered around the centre (same every run).
List<SMapPoint> _points() {
final random = math.Random(7);
return List.generate(2000, (i) {
final r = 0.25 * math.sqrt(random.nextDouble());
final a = random.nextDouble() * 2 * math.pi;
return SMapPoint(
center.latitude + r * math.sin(a) * 0.72,
center.longitude + r * math.cos(a),
properties: {'name': 'Stop ${i + 1}'},
);
});
}
Future<void> _onReady(SMapController map) async {
_map = map;
await map.addPolygons(
'block',
[
const SMapPolygon([
[LatLng(43.6418, -79.3871), LatLng(43.6418, -79.3833), LatLng(43.6440, -79.3833), LatLng(43.6440, -79.3871)],
], properties: {'name': 'Tower block', 'height': 180}),
],
const SMapPolygonOptions(fill: '#f97316', extrude: SMapExtrude(height: 'height')),
);
await map.addLines(
'route',
[
const SMapLine([
LatLng(43.6455, -79.3807),
LatLng(43.6532, -79.3832),
LatLng(43.6629, -79.3957),
LatLng(43.6677, -79.3948),
], properties: {'name': 'Route 1'}),
],
const SMapLineOptions(color: '#16a34a', width: 5),
);
await map.addPoints('stops', _points(), const SMapPointOptions(cluster: true, label: 'name', zIndex: 1));
map.onTap('stops', (properties, position) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('${properties['name']}')));
});
map.onTap('route', (properties, position) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('${properties['name']}')));
});
}
void _onError(Object error) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('$error')));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('sMap'),
actions: [
DropdownButton<String>(
value: _language,
underline: const SizedBox.shrink(),
items: [for (final l in languages) DropdownMenuItem(value: l, child: Text(l))],
onChanged: (l) {
if (l == null) return;
setState(() => _language = l);
_map?.setLanguage(l);
},
),
const SizedBox(width: 8),
Row(children: [
const Text('3D'),
Switch(
value: _buildings,
onChanged: (on) {
setState(() => _buildings = on);
_map?.setBuildings3D(on);
},
),
]),
],
),
body: Column(
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: SegmentedButton<SMapStyle>(
segments: [for (final s in SMapStyle.values) ButtonSegment(value: s, label: Text(s.name))],
selected: {_style},
showSelectedIcon: false,
onSelectionChanged: (set) {
setState(() => _style = set.first);
_map?.setStyle(set.first);
},
),
),
Expanded(
child: SMap(
options: SMapOptions(
baseUrl: baseUrl,
token: token.isEmpty ? null : token,
apiKey: apiKey.isEmpty ? null : apiKey,
style: _style,
language: _language,
center: center,
zoom: 11,
tools: SMapToolsOptions(
hidden: (prefs.getStringList(toolsKey) ?? const []).toSet(),
onChange: (change) => prefs.setStringList(toolsKey, change.hidden),
),
),
tools: [
SMapTool.kind('zoom', SMapToolKind.zoom),
SMapTool.kind('compass', SMapToolKind.compass),
SMapTool.kind('scale', SMapToolKind.scale),
SMapTool(
id: 'legend',
label: 'Legend',
position: SMapToolPosition.bottomLeft,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(6)),
child: const Text('Green: route · Orange: block', style: TextStyle(fontSize: 12)),
),
),
],
onReady: _onReady,
onError: _onError,
),
),
],
),
floatingActionButton: FloatingActionButton.small(
onPressed: () => _map?.fitLayer('stops'),
tooltip: 'Show all stops',
child: const Icon(Icons.fit_screen),
),
);
}
}