diff --git a/.gitignore b/.gitignore index bee8a647549..bbd74195c0f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ config.gypi config.mk .DS_Store +out +mapnik-packaging +*.xcodeproj diff --git a/Makefile b/Makefile index 7bf075bb6dd..d2cffacb8e7 100644 --- a/Makefile +++ b/Makefile @@ -24,15 +24,24 @@ xapp: xcode config.gypi src macosx/llmr-app.gyp xcodebuild -project ./macosx/llmr-app.xcodeproj open macosx/build/Release/llmr.app +# build iOS app with xcodebuild +iapp: xcode config.gypi src ios/llmr-app.gyp + deps/run_gyp ios/llmr-app.gyp -Goutput_dir=./out/ --depth=. --generator-output=./ -f xcode + xcodebuild -project ./ios/llmr-app.xcodeproj + # launch app with ios-sim? + clean: -rm -rf out -rm -rf build -rm -rf macosx/build + -rm -rf ios/build distclean: -rm -f config.gypi -rm -f config.mk -rm -rf llmr.xcodeproj + -rm -rf macosx/llmr-app.xcodeproj + -rm -rf ios/llmr-app.xcodeproj test: all echo test diff --git a/include/llmr/map/map.hpp b/include/llmr/map/map.hpp index 7950158e350..5c18c879188 100644 --- a/include/llmr/map/map.hpp +++ b/include/llmr/map/map.hpp @@ -25,6 +25,7 @@ class Map { Map &operator=(const Map&) = delete; Map &operator=(const Map&&) = delete; + /* setup */ void setup(); void loadStyle(const uint8_t *const data, uint32_t bytes); void loadSprite(const std::string& url); @@ -33,19 +34,29 @@ class Map { /* callback */ bool render(); - void moveBy(double dx, double dy); - void scaleBy(double ds, double cx, double cy); - void rotateBy(double cx, double cy, double sx, double sy, double ex, double ey); void tileLoaded(std::shared_ptr tile); void tileFailed(std::shared_ptr tile); /* position */ - void resetNorth(); - void resetPosition(); - // void setAngle(double angle); + void moveBy(double dx, double dy); // void setLonLat(double lon, double lat); + void resetPosition(); + + /* scale */ + void scaleBy(double ds, double cx, double cy); + void setScale(double scale); + double getScale() const; // void setZoom(double zoom); // void setLonLatZoom(double lon, double lat, double zoom); + // double getZoom() const; + // void getLonLatZoom(double &lon, double &lat, double &zoom) const; + // void resetZoom(); + + /* rotation */ + void rotateBy(double cx, double cy, double sx, double sy, double ex, double ey); + void setAngle(double angle); + double getAngle() const; + void resetNorth(); void toggleDebug(); diff --git a/include/llmr/platform/platform.hpp b/include/llmr/platform/platform.hpp index f729c4e21ad..b31a112b87c 100644 --- a/include/llmr/platform/platform.hpp +++ b/include/llmr/platform/platform.hpp @@ -5,8 +5,8 @@ #include #include -#define kTileURL "http://localhost:3333/gl/tiles/plain/%d-%d-%d.vector.pbf" -#define kSpriteURL "http://localhost:3333/gl/debug/img/sprite" +#define kTileURL "http://mapbox:magic@kkaefer.net/gl/tiles/plain/%d-%d-%d.vector.pbf" +#define kSpriteURL "http://mapbox:magic@kkaefer.net/gl/debug/img/sprite" namespace llmr { diff --git a/ios/Info.plist b/ios/Info.plist new file mode 100644 index 00000000000..d78b5428e13 --- /dev/null +++ b/ios/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.mapbox.llmr.native + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 0.0.1 + CFBundleSignature + LLMR + CFBundleVersion + 0.0.1 + LSRequiresIPhoneOS + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + NSHumanReadableCopyright + (c) 2014 Mapbox + + diff --git a/ios/MBXAppDelegate.h b/ios/MBXAppDelegate.h new file mode 100644 index 00000000000..10234333206 --- /dev/null +++ b/ios/MBXAppDelegate.h @@ -0,0 +1,15 @@ +// +// MBXAppDelegate.h +// ios +// +// Created by Justin R. Miller on 1/27/14. +// +// + +#import + +@interface MBXAppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end \ No newline at end of file diff --git a/ios/MBXAppDelegate.m b/ios/MBXAppDelegate.m new file mode 100644 index 00000000000..4b860a361b7 --- /dev/null +++ b/ios/MBXAppDelegate.m @@ -0,0 +1,24 @@ +// +// MBXAppDelegate.m +// ios +// +// Created by Justin R. Miller on 1/27/14. +// +// + +#import "MBXAppDelegate.h" + +#import "MBXViewController.h" + +@implementation MBXAppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window.rootViewController = [MBXViewController new]; + [self.window makeKeyAndVisible]; + + return YES; +} + +@end \ No newline at end of file diff --git a/ios/MBXSettings.h b/ios/MBXSettings.h new file mode 100644 index 00000000000..fa0e55c7241 --- /dev/null +++ b/ios/MBXSettings.h @@ -0,0 +1,21 @@ +// +// MBXSettings.hpp +// llmr +// +// Created by Justin R. Miller on 1/27/14. +// +// + +#import + +namespace llmr +{ + class Settings_iOS : public Settings + { + public: + Settings_iOS(); + virtual void save(); + virtual void load(); + virtual void clear(); + }; +} \ No newline at end of file diff --git a/ios/MBXSettings.mm b/ios/MBXSettings.mm new file mode 100644 index 00000000000..a19db0dab3d --- /dev/null +++ b/ios/MBXSettings.mm @@ -0,0 +1,68 @@ +// +// MBXSettings.cpp +// llmr +// +// Created by Justin R. Miller on 1/27/14. +// +// + +#import "MBXSettings.h" + +#import + +using namespace llmr; + +Settings_iOS::Settings_iOS() { + NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys: + + // position + [NSNumber numberWithDouble:longitude], @"longitude", + [NSNumber numberWithDouble:latitude], @"latitude", + [NSNumber numberWithDouble:scale], @"scale", + [NSNumber numberWithDouble:angle], @"angle", + + // debug + [NSNumber numberWithBool:debug], @"debug", + + nil + ]; + + [[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults]; +} + +void Settings_iOS::load() { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + + // position + longitude = [defaults doubleForKey:@"longitude"]; + latitude = [defaults doubleForKey:@"latitude"]; + scale = [defaults doubleForKey:@"scale"]; + angle = [defaults doubleForKey:@"angle"]; + + // debug + debug = [defaults boolForKey:@"debug"]; +} + +void Settings_iOS::save() { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + + NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys: + + // position + [NSNumber numberWithDouble:longitude], @"longitude", + [NSNumber numberWithDouble:latitude], @"latitude", + [NSNumber numberWithDouble:scale], @"scale", + [NSNumber numberWithDouble:angle], @"angle", + + // debug + [NSNumber numberWithBool:debug], @"debug", + + nil + ]; +// [defaults setPersistentDomain:appDefaults forName:[[NSBundle mainBundle] bundleIdentifier]]; + +// [defaults synchronize]; +} + +void Settings_iOS::clear() { +} \ No newline at end of file diff --git a/ios/MBXViewController.h b/ios/MBXViewController.h new file mode 100644 index 00000000000..a554a726176 --- /dev/null +++ b/ios/MBXViewController.h @@ -0,0 +1,14 @@ +// +// MBXViewController.h +// ios +// +// Created by Justin R. Miller on 1/27/14. +// +// + +#import +#import + +@interface MBXViewController : GLKViewController + +@end \ No newline at end of file diff --git a/ios/MBXViewController.mm b/ios/MBXViewController.mm new file mode 100644 index 00000000000..62cd8ce0136 --- /dev/null +++ b/ios/MBXViewController.mm @@ -0,0 +1,256 @@ +// +// MBXViewController.m +// ios +// +// Created by Justin R. Miller on 1/27/14. +// +// + +#import "MBXViewController.h" + +#import "MBXSettings.h" + +#import +#import + +#include +#include + +@interface MBXViewController () + +@property (nonatomic) EAGLContext *context; +@property (nonatomic) CGPoint center; +@property (nonatomic) CGFloat zoom; +@property (nonatomic) CGFloat angle; + +@end + +@implementation MBXViewController + +class MBXMapView +{ + public: + MBXMapView() : settings(), map(settings) + { + } + + ~MBXMapView() + { + } + + void init() + { + settings.load(); + + map.setup(); + + CGRect frame = [[UIScreen mainScreen] bounds]; + map.resize(frame.size.width, frame.size.height, frame.size.width, frame.size.height); + + map.loadSettings(); + } + + public: + llmr::Settings_iOS settings; + llmr::Map map; +}; + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; + + if ( ! self.context) + { + NSLog(@"Failed to initialize OpenGL ES context"); + return; + } + + mapView = new MBXMapView(); + + GLKView *view = (GLKView *)self.view; + view.context = self.context; + view.drawableStencilFormat = GLKViewDrawableStencilFormat8; + [view bindDrawable]; + + [EAGLContext setCurrentContext:self.context]; + + displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render:)]; + [displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; + + mapView->init(); + + UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; + [self.view addGestureRecognizer:pan]; + + UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTapGesture:)]; + doubleTap.numberOfTapsRequired = 2; + [self.view addGestureRecognizer:doubleTap]; + + UITapGestureRecognizer *twoFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTwoFingerTapGesture:)]; + twoFingerTap.numberOfTouchesRequired = 2; + [self.view addGestureRecognizer:twoFingerTap]; + + UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGesture:)]; + [self.view addGestureRecognizer:longPress]; + + UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)]; + [self.view addGestureRecognizer:pinch]; + + UIRotationGestureRecognizer *rotate = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotateGesture:)]; + [self.view addGestureRecognizer:rotate]; +} + +- (void)render:(id)sender +{ + mapView->map.render(); + + static double frames = 0; + static double elapsed = 0; + + frames++; + + double current = [[NSDate date] timeIntervalSince1970]; + + if (current - elapsed >= 1) + { + NSLog(@"FPS: %f", frames / (current - elapsed)); + elapsed = current; + frames = 0; + } +} + +- (void)handlePanGesture:(UIPanGestureRecognizer *)pan +{ + if (pan.state == UIGestureRecognizerStateBegan) + self.center = CGPointMake(0, 0); + + CGPoint delta = CGPointMake([pan translationInView:pan.view].x - self.center.x, + [pan translationInView:pan.view].y - self.center.y); + + mapView->map.moveBy(delta.x, delta.y); + + self.center = CGPointMake(self.center.x + delta.x, self.center.y + delta.y); +} + +- (void)handleDoubleTapGesture:(UIPanGestureRecognizer *)doubleTap +{ + CGPoint gesturePoint = [doubleTap locationInView:doubleTap.view]; + + mapView->map.scaleBy(2, gesturePoint.x, gesturePoint.y); +} + +- (void)handleTwoFingerTapGesture:(UIPanGestureRecognizer *)twoFingerTap +{ + CGPoint gesturePoint = [twoFingerTap locationInView:twoFingerTap.view]; + + mapView->map.scaleBy(0.5, gesturePoint.x, gesturePoint.y); +} + +- (void)handleLongPressGesture:(UILongPressGestureRecognizer *)longPress +{ + mapView->map.resetNorth(); +} + +- (void)handlePinchGesture:(UIPinchGestureRecognizer *)pinch +{ + CGFloat scale = mapView->map.getScale(); + + if (pinch.state == UIGestureRecognizerStateBegan) + self.zoom = log2f(scale); + + CGFloat newZoom = self.zoom + (pinch.scale > 1 ? ((pinch.scale / 10) * 1) : (((1 - pinch.scale) / 0.9) * -1)); + + mapView->map.setScale(powf(2, newZoom)); +} + +- (void)handleRotateGesture:(UIRotationGestureRecognizer *)rotate +{ + if (rotate.state == UIGestureRecognizerStateBegan) + self.angle = mapView->map.getAngle(); + + mapView->map.setAngle(self.angle + rotate.rotation); +} + +CADisplayLink *displayLink; +MBXMapView *mapView; +NSOperationQueue *queue; + +namespace llmr +{ + namespace platform + { + void restart(void *) + { + } + + void async(std::function fn, std::function cb) + { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) + { + fn(); + + dispatch_async(dispatch_get_main_queue(), ^(void) + { + cb(); + }); + }); + } + + void request_http(std::string url, std::function func) + { + NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithUTF8String:url.c_str()]]]; + + [NSURLConnection sendAsynchronousRequest:urlRequest + queue:[NSOperationQueue mainQueue] + completionHandler: ^(NSURLResponse *response, NSData *data, NSError *error) + { + if ( ! error) + { + Response res; + + res.code = [(NSHTTPURLResponse *)response statusCode]; + res.body = { (const char *)[data bytes], [data length] }; + + func(res); + } + else + { + Response res; + + func(res); + } + }]; + } + + void request_http(std::string url, std::function func, std::function cb) + { + NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithUTF8String:url.c_str()]]]; + + [NSURLConnection sendAsynchronousRequest:urlRequest + queue:[NSOperationQueue mainQueue] + completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) + { + Response res; + + if (error == nil) + { + res.code = [(NSHTTPURLResponse *)response statusCode]; + res.body = { (const char *)[data bytes], [data length] }; + } + + func(res); + + cb(); + }]; + } + + double time() + { + return [displayLink timestamp]; + } + } +} + +@end \ No newline at end of file diff --git a/ios/README.md b/ios/README.md new file mode 100644 index 00000000000..b0dad9e14ac --- /dev/null +++ b/ios/README.md @@ -0,0 +1,8 @@ +llmr-ios +======== + +1. Because `libpng` isn't included in the iOS SDK, you will need to build a cross-architecture version yourself. Run `./setup_libpng.sh`, which is derived from Mapnik's cross-architecture build scripts. + +1. `make iapp` to create and build the iOS test app Xcode project. + +1. Consider `sudo npm install -g ios-sim` for auto-launching the simulator, but it can be tricky and it's better to run on an ARM-based device anyway. Open `./llmr-app.xcodeproj`, then build and run on the simulator or a device yourself. \ No newline at end of file diff --git a/ios/img/Icon-60.png b/ios/img/Icon-60.png new file mode 100644 index 00000000000..d062c32754a Binary files /dev/null and b/ios/img/Icon-60.png differ diff --git a/ios/img/Icon-60@2x.png b/ios/img/Icon-60@2x.png new file mode 100644 index 00000000000..84550f36e4a Binary files /dev/null and b/ios/img/Icon-60@2x.png differ diff --git a/ios/img/Icon-72.png b/ios/img/Icon-72.png new file mode 100644 index 00000000000..9218aca973b Binary files /dev/null and b/ios/img/Icon-72.png differ diff --git a/ios/img/Icon-72@2x.png b/ios/img/Icon-72@2x.png new file mode 100644 index 00000000000..b4fb12ba7b0 Binary files /dev/null and b/ios/img/Icon-72@2x.png differ diff --git a/ios/img/Icon-76.png b/ios/img/Icon-76.png new file mode 100644 index 00000000000..debb8c453ef Binary files /dev/null and b/ios/img/Icon-76.png differ diff --git a/ios/img/Icon-76@2x.png b/ios/img/Icon-76@2x.png new file mode 100644 index 00000000000..1dce0cbd50f Binary files /dev/null and b/ios/img/Icon-76@2x.png differ diff --git a/ios/img/Icon-Small-50.png b/ios/img/Icon-Small-50.png new file mode 100644 index 00000000000..a72e343a32d Binary files /dev/null and b/ios/img/Icon-Small-50.png differ diff --git a/ios/img/Icon-Small-50@2x.png b/ios/img/Icon-Small-50@2x.png new file mode 100644 index 00000000000..0535d00f0a9 Binary files /dev/null and b/ios/img/Icon-Small-50@2x.png differ diff --git a/ios/img/Icon-Small.png b/ios/img/Icon-Small.png new file mode 100644 index 00000000000..46d4a5de649 Binary files /dev/null and b/ios/img/Icon-Small.png differ diff --git a/ios/img/Icon-Small@2x.png b/ios/img/Icon-Small@2x.png new file mode 100644 index 00000000000..99058cebdbb Binary files /dev/null and b/ios/img/Icon-Small@2x.png differ diff --git a/ios/img/Icon-Spotlight-40.png b/ios/img/Icon-Spotlight-40.png new file mode 100644 index 00000000000..14b1ae1125f Binary files /dev/null and b/ios/img/Icon-Spotlight-40.png differ diff --git a/ios/img/Icon-Spotlight-40@2x.png b/ios/img/Icon-Spotlight-40@2x.png new file mode 100644 index 00000000000..4a45d8eff91 Binary files /dev/null and b/ios/img/Icon-Spotlight-40@2x.png differ diff --git a/ios/img/Icon.png b/ios/img/Icon.png new file mode 100644 index 00000000000..bb426e6681a Binary files /dev/null and b/ios/img/Icon.png differ diff --git a/ios/img/Icon@2x.png b/ios/img/Icon@2x.png new file mode 100644 index 00000000000..3069b485fa0 Binary files /dev/null and b/ios/img/Icon@2x.png differ diff --git a/ios/img/iTunesArtwork b/ios/img/iTunesArtwork new file mode 100644 index 00000000000..ac6a0c58e81 Binary files /dev/null and b/ios/img/iTunesArtwork differ diff --git a/ios/img/iTunesArtwork@2x b/ios/img/iTunesArtwork@2x new file mode 100644 index 00000000000..fae1dad8bf8 Binary files /dev/null and b/ios/img/iTunesArtwork@2x differ diff --git a/ios/llmr-app.gyp b/ios/llmr-app.gyp new file mode 100644 index 00000000000..f812eb1d7ea --- /dev/null +++ b/ios/llmr-app.gyp @@ -0,0 +1,51 @@ +{ + 'includes': [ + '../common.gypi', + '../config.gypi' + ], + 'targets': [ + { + "target_name": "iosapp", + "product_name": "llmr", + "type": "executable", + "sources": [ + "./main.m", + "./MBXAppDelegate.m", + "./MBXSettings.mm", + "./MBXViewController.mm" + ], + 'product_extension': 'app', + 'mac_bundle': 1, + 'mac_bundle_resources': [ + ' + +#import "MBXAppDelegate.h" + +int main(int argc, char * argv[]) +{ + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([MBXAppDelegate class])); + } +} diff --git a/ios/setup_libpng.sh b/ios/setup_libpng.sh new file mode 100755 index 00000000000..a66e06d5c1f --- /dev/null +++ b/ios/setup_libpng.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +cd ../ +if [ ! -d 'mapnik-packaging' ]; then + git clone --depth=0 https://github.com/mapnik/mapnik-packaging.git +fi +cd mapnik-packaging/osx/ +export CXX11=true +(source iPhoneSimulator.sh; ./scripts/build_png.sh; \ + source MacOSX.sh; ./scripts/build_png.sh; \ + source iPhoneOS.sh; ./scripts/build_png.sh; \ + source iPhoneOSs.sh; ./scripts/build_png.sh; \ + source iPhoneOS64.sh; ./scripts/build_png.sh; \ + ./scripts/make_universal.sh) +export UNIVERSAL_LIBS=`pwd`/out/build-cpp11-libcpp-universal/ +export PNG_INCLUDES=`pwd`/out/build-cpp11-libcpp-i386/ +cd ../../ +./configure --png-libpath=${UNIVERSAL_LIBS} --png-includes=${PNG_INCLUDES} \ No newline at end of file diff --git a/src/map/map.cpp b/src/map/map.cpp index fabf26c08f4..6ed3c0284ce 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -83,6 +83,32 @@ void Map::rotateBy(double cx, double cy, double sx, double sy, double ex, double settings.save(); } +void Map::setScale(double scale) { + transform.setScale(scale); + style.cascade(transform.getZoom()); + update(); + + transform.getLonLat(settings.longitude, settings.latitude); + settings.scale = transform.getScale(); + settings.save(); +} + +void Map::setAngle(double angle) { + transform.setAngle(angle); + update(); + + settings.angle = transform.getAngle(); + settings.save(); +} + +double Map::getScale() const { + return transform.getScale(); +} + +double Map::getAngle() const { + return transform.getAngle(); +} + void Map::resetNorth() { transform.setAngle(0, 0.5); // 500 ms update(); diff --git a/src/renderer/shader.cpp b/src/renderer/shader.cpp index 311e46e4b28..f2326efd73b 100644 --- a/src/renderer/shader.cpp +++ b/src/renderer/shader.cpp @@ -105,8 +105,8 @@ bool Shader::compileShader(GLuint *shader, GLenum type, const GLchar *source) { *shader = glCreateShader(type); -#ifdef EMSCRIPTEN - // Add WebGL GLSL precision premable +#if defined(EMSCRIPTEN) || defined(GL_ES_VERSION_2_0) + // Add WebGL GLSL / OpenGL ES precision premable const GLchar *preamble = "precision mediump float;\n\n"; #else // Desktop GLSL