Created
August 28, 2024 04:25
-
-
Save bdon/ce3180498561fb0904b7d7ac6df441de to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
AWSTemplateFormatVersion: 2010-09-09 | |
Description: Serve Z/X/Y tiles through CloudFront + Lambda from an existing S3 bucket. | |
Parameters: | |
BucketName: | |
Description: >- | |
Name of an existing S3 bucket with .pmtiles tilesets. Should be in the | |
same region as your CloudFormation stack. | |
Type: String | |
MinLength: 1 | |
AllowedOrigins: | |
Description: >- | |
Comma-separated list of domains (e.g. example.com) allowed by browser CORS | |
policy, or * for all origins. | |
Type: List<String> | |
Default: '*' | |
PublicHostname: | |
Description: >- | |
Optional. Replace *.cloudfront.net in TileJSON with a custom hostname | |
(e.g. example.com). See docs on how this value is cached. | |
Type: String | |
Outputs: | |
CloudFrontDistributionUrl: | |
Description: URL of the CloudFront distribution | |
Value: !Sub https://${CloudFrontDistribution.DomainName} | |
Export: | |
Name: !Sub ${AWS::StackName}-CloudFrontDistributionURL | |
Conditions: | |
IsPublicHostnameProvided: !Not | |
- !Equals | |
- !Ref PublicHostname | |
- '' | |
Resources: | |
LogGroup: | |
Type: AWS::Logs::LogGroup | |
Properties: | |
LogGroupName: !Sub /aws/lambda/${AWS::StackName} | |
RetentionInDays: 7 | |
LambdaExecutionRole: | |
Type: AWS::IAM::Role | |
Properties: | |
AssumeRolePolicyDocument: | |
Version: 2012-10-17 | |
Statement: | |
- Effect: Allow | |
Principal: | |
Service: lambda.amazonaws.com | |
Action: sts:AssumeRole | |
Policies: | |
- PolicyName: !Sub ${AWS::StackName}-LambdaLoggingPolicy | |
PolicyDocument: | |
Version: 2012-10-17 | |
Statement: | |
- Effect: Allow | |
Action: | |
- logs:CreateLogStream | |
- logs:PutLogEvents | |
Resource: !Sub >- | |
arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${AWS::StackName}:log-stream:* | |
- PolicyName: !Sub ${AWS::StackName}-LambdaS3AccessPolicy | |
PolicyDocument: | |
Version: 2012-10-17 | |
Statement: | |
- Effect: Allow | |
Action: s3:GetObject | |
Resource: !Sub arn:aws:s3:::${BucketName}/* | |
LambdaFunctionUrl: | |
Type: AWS::Lambda::Url | |
Properties: | |
AuthType: NONE | |
TargetFunctionArn: !GetAtt LambdaFunction.Arn | |
InvokeMode: BUFFERED | |
LambdaFunctionUrlPermission: | |
Type: AWS::Lambda::Permission | |
Properties: | |
Action: lambda:InvokeFunctionUrl | |
FunctionName: !Ref LambdaFunction | |
Principal: '*' | |
FunctionUrlAuthType: NONE | |
ViewerRequestCloudFrontFunction: | |
Type: AWS::CloudFront::Function | |
Properties: | |
Name: !Sub ${AWS::StackName}-ViewerRequestCloudFrontFunction | |
AutoPublish: true | |
FunctionCode: | | |
function handler(event) { | |
const request = event.request; | |
request.headers['x-distribution-domain-name'] = { value: event.context.distributionDomainName }; | |
return request; | |
} | |
FunctionConfig: | |
Comment: Add x-distribution-domain header. | |
Runtime: cloudfront-js-2.0 | |
CloudFrontDistribution: | |
Type: AWS::CloudFront::Distribution | |
DeletionPolicy: Delete | |
Properties: | |
DistributionConfig: | |
Origins: | |
- Id: LambdaOrigin | |
DomainName: !Select | |
- 2 | |
- !Split | |
- / | |
- !GetAtt LambdaFunctionUrl.FunctionUrl | |
CustomOriginConfig: | |
OriginProtocolPolicy: https-only | |
DefaultCacheBehavior: | |
TargetOriginId: LambdaOrigin | |
ViewerProtocolPolicy: redirect-to-https | |
CachePolicyId: !Ref CachePolicyId | |
ResponseHeadersPolicyId: !Ref ResponseHeadersPolicyId | |
OriginRequestPolicyId: b689b0a8-53d0-40ab-baf2-68738e2966ac | |
FunctionAssociations: !If | |
- IsPublicHostnameProvided | |
- !Ref AWS::NoValue | |
- - EventType: viewer-request | |
FunctionARN: !GetAtt ViewerRequestCloudFrontFunction.FunctionARN | |
Enabled: true | |
HttpVersion: http2and3 | |
Comment: CloudFront Distribution | |
PriceClass: PriceClass_All | |
CachePolicyId: | |
Type: AWS::CloudFront::CachePolicy | |
Properties: | |
CachePolicyConfig: | |
Name: !Sub ${AWS::StackName}-CachePolicyConfig | |
DefaultTTL: 86400 | |
MaxTTL: 31536000 | |
MinTTL: 0 | |
ParametersInCacheKeyAndForwardedToOrigin: | |
EnableAcceptEncodingBrotli: true | |
EnableAcceptEncodingGzip: true | |
HeadersConfig: | |
HeaderBehavior: none | |
CookiesConfig: | |
CookieBehavior: none | |
QueryStringsConfig: | |
QueryStringBehavior: none | |
ResponseHeadersPolicyId: | |
Type: AWS::CloudFront::ResponseHeadersPolicy | |
Properties: | |
ResponseHeadersPolicyConfig: | |
Name: !Sub ${AWS::StackName}-ResponseHeadersPolicyConfig | |
CorsConfig: | |
AccessControlAllowOrigins: | |
Items: !Ref AllowedOrigins | |
AccessControlAllowHeaders: | |
Items: | |
- '*' | |
AccessControlAllowMethods: | |
Items: | |
- GET | |
- HEAD | |
- OPTIONS | |
AccessControlExposeHeaders: | |
Items: | |
- ETag | |
AccessControlAllowCredentials: false | |
OriginOverride: true | |
Comment: CORS policy for Protomaps | |
LambdaFunction: | |
Type: AWS::Lambda::Function | |
Properties: | |
FunctionName: !Sub ${AWS::StackName}-LambdaFunction | |
Runtime: nodejs20.x | |
Architectures: | |
- arm64 | |
Role: !GetAtt LambdaExecutionRole.Arn | |
Handler: index.handler | |
MemorySize: 512 | |
LoggingConfig: | |
LogGroup: !Ref LogGroup | |
Environment: | |
Variables: | |
BUCKET: !Ref BucketName | |
PUBLIC_HOSTNAME: !Ref PublicHostname | |
Code: | |
ZipFile: > | |
//61020e6 | |
"use strict";var Qe=Object.create;var G=Object.defineProperty;var | |
_e=Object.getOwnPropertyDescriptor;var | |
et=Object.getOwnPropertyNames;var | |
tt=Object.getPrototypeOf,rt=Object.prototype.hasOwnProperty;var | |
nt=(t,e)=>{for(var r in | |
e)G(t,r,{get:e[r],enumerable:!0})},Ae=(t,e,r,n)=>{if(e&&typeof | |
e=="object"||typeof e=="function")for(let i of | |
et(e))!rt.call(t,i)&&i!==r&&G(t,i,{get:()=>e[i],enumerable:!(n=_e(e,i))||n.enumerable});return | |
t};var | |
it=(t,e,r)=>(r=t!=null?Qe(tt(t)):{},Ae(e||!t||!t.__esModule?G(r,"default",{value:t,enumerable:!0}):r,t)),at=t=>Ae(G({},"__esModule",{value:!0}),t);var | |
qt={};nt(qt,{handler:()=>jt,handlerRaw:()=>je});module.exports=at(qt);var | |
De=require("module"),ot=(0,De.createRequire)("/"),st;try{st=ot("worker_threads").Worker}catch{}var | |
w=Uint8Array,M=Uint16Array,ut=Int32Array,ze=new | |
w([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0]),Ce=new | |
w([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0]),ft=new | |
w([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),Ee=function(t,e){for(var | |
r=new M(31),n=0;n<31;++n)r[n]=e+=1<<t[n-1];for(var i=new | |
ut(r[30]),n=1;n<30;++n)for(var | |
a=r[n];a<r[n+1];++a)i[a]=a-r[n]<<5|n;return{b:r,r:i}},Te=Ee(ze,2),Ue=Te.b,ct=Te.r;Ue[28]=258,ct[258]=28;var | |
Pe=Ee(Ce,0),lt=Pe.b,Jt=Pe.r,ne=new | |
M(32768);for(m=0;m<32768;++m)C=(m&43690)>>1|(m&21845)<<1,C=(C&52428)>>2|(C&13107)<<2,C=(C&61680)>>4|(C&3855)<<4,ne[m]=((C&65280)>>8|(C&255)<<8)>>1;var | |
C,m,H=function(t,e,r){for(var n=t.length,i=0,a=new | |
M(e);i<n;++i)t[i]&&++a[t[i]-1];var o=new | |
M(e);for(i=1;i<e;++i)o[i]=o[i-1]+a[i-1]<<1;var s;if(r){s=new | |
M(1<<e);var l=15-e;for(i=0;i<n;++i)if(t[i])for(var | |
c=i<<4|t[i],u=e-t[i],f=o[t[i]-1]++<<u,h=f|(1<<u)-1;f<=h;++f)s[ne[f]>>l]=c}else | |
for(s=new | |
M(n),i=0;i<n;++i)t[i]&&(s[i]=ne[o[t[i]-1]++]>>15-t[i]);return s},V=new | |
w(288);for(m=0;m<144;++m)V[m]=8;var m;for(m=144;m<256;++m)V[m]=9;var | |
m;for(m=256;m<280;++m)V[m]=7;var m;for(m=280;m<288;++m)V[m]=8;var | |
m,Le=new w(32);for(m=0;m<32;++m)Le[m]=5;var m;var ht=H(V,9,1);var | |
mt=H(Le,5,1),te=function(t){for(var | |
e=t[0],r=1;r<t.length;++r)t[r]>e&&(e=t[r]);return | |
e},A=function(t,e,r){var | |
n=e/8|0;return(t[n]|t[n+1]<<8)>>(e&7)&r},re=function(t,e){var | |
r=e/8|0;return(t[r]|t[r+1]<<8|t[r+2]<<16)>>(e&7)},pt=function(t){return(t+7)/8|0},vt=function(t,e,r){(e==null||e<0)&&(e=0),(r==null||r>t.length)&&(r=t.length);var | |
n=new w(r-e);return n.set(t.subarray(e,r)),n};var gt=["unexpected | |
EOF","invalid block type","invalid length/literal","invalid | |
distance","stream finished","no stream handler",,"no | |
callback","invalid UTF-8 data","extra field too long","date not in | |
range 1980-2099","filename too long","stream finishing","invalid zip | |
data"],d=function(t,e,r){var n=new | |
Error(e||gt[t]);if(n.code=t,Error.captureStackTrace&&Error.captureStackTrace(n,d),!r)throw | |
n;return n},ie=function(t,e,r,n){var | |
i=t.length,a=n?n.length:0;if(!i||e.f&&!e.l)return r||new w(0);var | |
o=!r||e.i!=2,s=e.i;r||(r=new w(i*3));var l=function(we){var | |
xe=r.length;if(we>xe){var be=new | |
w(Math.max(xe*2,we));be.set(r),r=be}},c=e.f||0,u=e.p||0,f=e.b||0,h=e.l,g=e.d,p=e.m,y=e.n,T=i*8;do{if(!h){c=A(t,u,1);var | |
F=A(t,u+1,3);if(u+=3,F)if(F==1)h=ht,g=mt,p=9,y=5;else if(F==2){var | |
J=A(t,u,31)+257,he=A(t,u+10,15)+4,me=J+A(t,u+5,31)+1;u+=14;for(var | |
B=new w(me),X=new | |
w(19),x=0;x<he;++x)X[ft[x]]=A(t,u+x*3,7);u+=he*3;for(var | |
pe=te(X),qe=(1<<pe)-1,We=H(X,pe,1),x=0;x<me;){var | |
ve=We[A(t,u,qe)];u+=ve&15;var v=ve>>4;if(v<16)B[x++]=v;else{var | |
U=0,$=0;for(v==16?($=3+A(t,u,3),u+=2,U=B[x-1]):v==17?($=3+A(t,u,7),u+=3):v==18&&($=11+A(t,u,127),u+=7);$--;)B[x++]=U}}var | |
ge=B.subarray(0,J),b=B.subarray(J);p=te(ge),y=te(b),h=H(ge,p,1),g=H(b,y,1)}else | |
d(1);else{var | |
v=pt(u)+4,q=t[v-4]|t[v-3]<<8,W=v+q;if(W>i){s&&d(0);break}o&&l(f+q),r.set(t.subarray(v,W),f),e.b=f+=q,e.p=u=W*8,e.f=c;continue}if(u>T){s&&d(0);break}}o&&l(f+131072);for(var | |
Je=(1<<p)-1,Xe=(1<<y)-1,Y=u;;Y=u){var | |
U=h[re(t,u)&Je],P=U>>4;if(u+=U&15,u>T){s&&d(0);break}if(U||d(2),P<256)r[f++]=P;else | |
if(P==256){Y=u,h=null;break}else{var ye=P-254;if(P>264){var | |
x=P-257,R=ze[x];ye=A(t,u,(1<<R)-1)+Ue[x],u+=R}var | |
Q=g[re(t,u)&Xe],_=Q>>4;Q||d(3),u+=Q&15;var b=lt[_];if(_>3){var | |
R=Ce[_];b+=re(t,u)&(1<<R)-1,u+=R}if(u>T){s&&d(0);break}o&&l(f+131072);var | |
ee=f+ye;if(f<b){var | |
de=a-b,Ye=Math.min(b,ee);for(de+f<0&&d(3);f<Ye;++f)r[f]=n[de+f]}for(;f<ee;f+=4)r[f]=r[f-b],r[f+1]=r[f+1-b],r[f+2]=r[f+2-b],r[f+3]=r[f+3-b];f=ee}}e.l=h,e.p=Y,e.b=f,e.f=c,h&&(c=1,e.m=p,e.d=g,e.n=y)}while(!c);return | |
f==r.length?r:vt(r,0,f)};var yt=new w(0);var | |
dt=function(t){(t[0]!=31||t[1]!=139||t[2]!=8)&&d(6,"invalid gzip | |
data");var e=t[3],r=10;e&4&&(r+=(t[10]|t[11]<<8)+2);for(var | |
n=(e>>3&1)+(e>>4&1);n>0;n-=!t[r++]);return r+(e&2)},wt=function(t){var | |
e=t.length;return(t[e-4]|t[e-3]<<8|t[e-2]<<16|t[e-1]<<24)>>>0};var | |
xt=function(t,e){return((t[0]&15)!=8||t[0]>>4>7||(t[0]<<8|t[1])%31)&&d(6,"invalid | |
zlib data"),(t[1]>>5&1)==+!e&&d(6,"invalid zlib data: | |
"+(t[1]&32?"need":"unexpected")+" dictionary"),(t[1]>>3&4)+2};function | |
bt(t,e){return ie(t,{i:2},e&&e.out,e&&e.dictionary)}function | |
At(t,e){var r=dt(t);return r+8>t.length&&d(6,"invalid gzip | |
data"),ie(t.subarray(r,-8),{i:2},e&&e.out||new | |
w(wt(t)),e&&e.dictionary)}function Dt(t,e){return | |
ie(t.subarray(xt(t,e&&e.dictionary),-4),{i:2},e&&e.out,e&&e.dictionary)}function | |
I(t,e){return | |
t[0]==31&&t[1]==139&&t[2]==8?At(t,e):(t[0]&15)!=8||t[0]>>4>7||(t[0]<<8|t[1])%31?bt(t,e):Dt(t,e)}var | |
zt=typeof TextDecoder<"u"&&new | |
TextDecoder,Ct=0;try{zt.decode(yt,{stream:!0}),Ct=1}catch{}var | |
Be=(t,e)=>t*2**e,Z=(t,e)=>Math.floor(t/2**e),K=(t,e)=>Be(t.getUint16(e+1,!0),8)+t.getUint8(e),Re=(t,e)=>Be(t.getUint32(e+2,!0),16)+t.getUint16(e,!0),Et=(t,e,r,n,i)=>{if(t!==n.getUint8(i))return | |
t-n.getUint8(i);let a=K(n,i+1);if(e!==a)return e-a;let | |
o=K(n,i+4);return r!==o?r-o:0},Tt=(t,e,r,n)=>{let | |
i=He(t,e|128,r,n);return | |
i?{z:e,x:r,y:n,offset:i[0],length:i[1],isDir:!0}:null},Me=(t,e,r,n)=>{let | |
i=He(t,e,r,n);return | |
i?{z:e,x:r,y:n,offset:i[0],length:i[1],isDir:!1}:null},He=(t,e,r,n)=>{let | |
i=0,a=t.byteLength/17-1;for(;i<=a;){let | |
o=a+i>>1,s=Et(e,r,n,t,o*17);if(s>0)i=o+1;else if(s<0)a=o-1;else | |
return[Re(t,o*17+7),t.getUint32(o*17+13,!0)]}return | |
null},Ut=(t,e)=>t.isDir&&!e.isDir?1:!t.isDir&&e.isDir?-1:t.z!==e.z?t.z-e.z:t.x!==e.x?t.x-e.x:t.y-e.y,Ve=(t,e)=>{let | |
r=t.getUint8(e*17);return{z:r&127,x:K(t,e*17+1),y:K(t,e*17+4),offset:Re(t,e*17+7),length:t.getUint32(e*17+13,!0),isDir:r>>7===1}},Se=t=>{let | |
e=[],r=new DataView(t);for(let | |
n=0;n<r.byteLength/17;n++)e.push(Ve(r,n));return | |
Pt(e)},Pt=t=>{t.sort(Ut);let e=new ArrayBuffer(17*t.length),r=new | |
Uint8Array(e);for(let n=0;n<t.length;n++){let | |
i=t[n],a=i.z;i.isDir&&(a=a|128),r[n*17]=a,r[n*17+1]=i.x&255,r[n*17+2]=i.x>>8&255,r[n*17+3]=i.x>>16&255,r[n*17+4]=i.y&255,r[n*17+5]=i.y>>8&255,r[n*17+6]=i.y>>16&255,r[n*17+7]=i.offset&255,r[n*17+8]=Z(i.offset,8)&255,r[n*17+9]=Z(i.offset,16)&255,r[n*17+10]=Z(i.offset,24)&255,r[n*17+11]=Z(i.offset,32)&255,r[n*17+12]=Z(i.offset,48)&255,r[n*17+13]=i.length&255,r[n*17+14]=i.length>>8&255,r[n*17+15]=i.length>>16&255,r[n*17+16]=i.length>>24&255}return | |
e},Lt=(t,e)=>{if(t.byteLength<17)return null;let | |
r=t.byteLength/17,n=Ve(t,r-1);if(n.isDir){let | |
i=n.z,a=e.z-i,o=Math.trunc(e.x/(1<<a)),s=Math.trunc(e.y/(1<<a));return{z:i,x:o,y:s}}return | |
null};async function Mt(t){let e=await t.getBytes(0,512e3),r=new | |
DataView(e.data),n=r.getUint32(4,!0),i=r.getUint16(8,!0),a=new | |
TextDecoder("utf-8"),o=JSON.parse(a.decode(new | |
DataView(e.data,10,n))),s=0;o.compression==="gzip"&&(s=2);let | |
l=0;"minzoom"in o&&(l=+o.minzoom);let c=0;"maxzoom"in | |
o&&(c=+o.maxzoom);let | |
u=0,f=0,h=0,g=-180,p=-85,y=180,T=85;if(o.bounds){let | |
v=o.bounds.split(",");g=+v[0],p=+v[1],y=+v[2],T=+v[3]}if(o.center){let | |
v=o.center.split(",");u=+v[0],f=+v[1],h=+v[2]}return{specVersion:r.getUint16(2,!0),rootDirectoryOffset:10+n,rootDirectoryLength:i*17,jsonMetadataOffset:10,jsonMetadataLength:n,leafDirectoryOffset:0,leafDirectoryLength:void | |
0,tileDataOffset:0,tileDataLength:void | |
0,numAddressedTiles:0,numTileEntries:0,numTileContents:0,clustered:!1,internalCompression:1,tileCompression:s,tileType:1,minZoom:l,maxZoom:c,minLon:g,minLat:p,maxLon:y,maxLat:T,centerZoom:h,centerLon:u,centerLat:f,etag:e.etag}}async | |
function St(t,e,r,n,i,a,o){let s=await | |
r.getArrayBuffer(e,t.rootDirectoryOffset,t.rootDirectoryLength,t);t.specVersion===1&&(s=Se(s));let | |
l=Me(new DataView(s),n,i,a);if(l){let f=(await | |
e.getBytes(l.offset,l.length,o)).data,h=new DataView(f);return | |
h.getUint8(0)===31&&h.getUint8(1)===139&&(f=I(new | |
Uint8Array(f))),{data:f}}let c=Lt(new | |
DataView(s),{z:n,x:i,y:a});if(c){let u=Tt(new | |
DataView(s),c.z,c.x,c.y);if(u){let f=await | |
r.getArrayBuffer(e,u.offset,u.length,t);t.specVersion===1&&(f=Se(f));let | |
h=Me(new DataView(f),n,i,a);if(h){let p=(await | |
e.getBytes(h.offset,h.length,o)).data,y=new DataView(p);return | |
y.getUint8(0)===31&&y.getUint8(1)===139&&(p=I(new | |
Uint8Array(p))),{data:p}}}}}var ae={getHeader:Mt,getZxy:St};function | |
S(t,e){return(e>>>0)*4294967296+(t>>>0)}function Rt(t,e){let | |
r=e.buf,n=r[e.pos++],i=(n&112)>>4;if(n<128||(n=r[e.pos++],i|=(n&127)<<3,n<128)||(n=r[e.pos++],i|=(n&127)<<10,n<128)||(n=r[e.pos++],i|=(n&127)<<17,n<128)||(n=r[e.pos++],i|=(n&127)<<24,n<128)||(n=r[e.pos++],i|=(n&1)<<31,n<128))return | |
S(t,i);throw new Error("Expected varint not more than 10 | |
bytes")}function k(t){let e=t.buf,r=e[t.pos++],n=r&127;return | |
r<128||(r=e[t.pos++],n|=(r&127)<<7,r<128)||(r=e[t.pos++],n|=(r&127)<<14,r<128)||(r=e[t.pos++],n|=(r&127)<<21,r<128)?n:(r=e[t.pos],n|=(r&15)<<28,Rt(n,t))}function | |
Ht(t,e,r,n){if(n===0){r===1&&(e[0]=t-1-e[0],e[1]=t-1-e[1]);let | |
i=e[0];e[0]=e[1],e[1]=i}}var | |
Vt=[0,1,5,21,85,341,1365,5461,21845,87381,349525,1398101,5592405,22369621,89478485,357913941,1431655765,5726623061,22906492245,91625968981,366503875925,1466015503701,5864062014805,23456248059221,93824992236885,375299968947541,0x5555555555555];function | |
It(t,e,r){if(t>26)throw Error("Tile zoom level exceeds max safe number | |
limit (26)");if(e>2**t-1||r>2**t-1)throw Error("tile x/y outside zoom | |
level bounds");let | |
n=Vt[t],i=2**t,a=0,o=0,s=0,l=[e,r],c=i/2;for(;c>0;)a=(l[0]&c)>0?1:0,o=(l[1]&c)>0?1:0,s+=c*c*(3*a^o),Ht(c,l,a,o),c=c/2;return | |
n+s}async function fe(t,e){if(e===1||e===0)return | |
t;if(e===2){if(typeof globalThis.DecompressionStream>"u")return I(new | |
Uint8Array(t));let r=new Response(t).body;if(!r)throw Error("Failed to | |
read response stream");let n=r.pipeThrough(new | |
globalThis.DecompressionStream("gzip"));return new | |
Response(n).arrayBuffer()}throw Error("Compression method not | |
supported")}var Zt=127;function Ot(t,e){let | |
r=0,n=t.length-1;for(;r<=n;){let | |
i=n+r>>1,a=e-t[i].tileId;if(a>0)r=i+1;else if(a<0)n=i-1;else return | |
t[i]}return | |
n>=0&&(t[n].runLength===0||e-t[n].tileId<t[n].runLength)?t[n]:null}var | |
se=class{constructor(e,r=new | |
Headers){this.url=e,this.customHeaders=r,this.mustReload=!1}getKey(){return | |
this.url}setHeaders(e){this.customHeaders=e}async | |
getBytes(e,r,n,i){let a,o;n?o=n:(a=new AbortController,o=a.signal);let | |
s=new | |
Headers(this.customHeaders);s.set("range",`bytes=${e}-${e+r-1}`);let | |
l;this.mustReload&&(l="reload");let c=await | |
fetch(this.url,{signal:o,cache:l,headers:s});if(e===0&&c.status===416){let | |
g=c.headers.get("Content-Range");if(!g||!g.startsWith("bytes | |
*/"))throw Error("Missing content-length on 416 response");let | |
p=+g.substr(8);c=await | |
fetch(this.url,{signal:o,cache:"reload",headers:{range:`bytes=0-${p-1}`}})}let | |
u=c.headers.get("Etag");if(u?.startsWith("W/")&&(u=null),c.status===416||i&&u&&u!==i)throw | |
this.mustReload=!0,new E(`Server returned non-matching ETag ${i} after | |
one retry. Check browser extensions and servers for issues that may | |
affect correct ETag headers.`);if(c.status>=300)throw Error(`Bad | |
response code: ${c.status}`);let | |
f=c.headers.get("Content-Length");if(c.status===200&&(!f||+f>r))throw | |
a&&a.abort(),Error("Server returned no content-length header or | |
content-length exceeding request. Check that your storage backend | |
supports HTTP Byte Serving.");return{data:await | |
c.arrayBuffer(),etag:u||void | |
0,cacheControl:c.headers.get("Cache-Control")||void | |
0,expires:c.headers.get("Expires")||void 0}}};function D(t,e){let | |
r=t.getUint32(e+4,!0),n=t.getUint32(e+0,!0);return r*2**32+n}function | |
kt(t,e){let r=new DataView(t),n=r.getUint8(7);if(n>3)throw | |
Error(`Archive is spec version ${n} but this library supports up to | |
spec version | |
3`);return{specVersion:n,rootDirectoryOffset:D(r,8),rootDirectoryLength:D(r,16),jsonMetadataOffset:D(r,24),jsonMetadataLength:D(r,32),leafDirectoryOffset:D(r,40),leafDirectoryLength:D(r,48),tileDataOffset:D(r,56),tileDataLength:D(r,64),numAddressedTiles:D(r,72),numTileEntries:D(r,80),numTileContents:D(r,88),clustered:r.getUint8(96)===1,internalCompression:r.getUint8(97),tileCompression:r.getUint8(98),tileType:r.getUint8(99),minZoom:r.getUint8(100),maxZoom:r.getUint8(101),minLon:r.getInt32(102,!0)/1e7,minLat:r.getInt32(106,!0)/1e7,maxLon:r.getInt32(110,!0)/1e7,maxLat:r.getInt32(114,!0)/1e7,centerZoom:r.getUint8(118),centerLon:r.getInt32(119,!0)/1e7,centerLat:r.getInt32(123,!0)/1e7,etag:e}}function | |
Ie(t){let e={buf:new Uint8Array(t),pos:0},r=k(e),n=[],i=0;for(let | |
a=0;a<r;a++){let | |
o=k(e);n.push({tileId:i+o,offset:0,length:0,runLength:1}),i+=o}for(let | |
a=0;a<r;a++)n[a].runLength=k(e);for(let | |
a=0;a<r;a++)n[a].length=k(e);for(let a=0;a<r;a++){let | |
o=k(e);o===0&&a>0?n[a].offset=n[a-1].offset+n[a-1].length:n[a].offset=o-1}return | |
n}function Ft(t){let e=new DataView(t);return | |
e.getUint16(2,!0)===2?(console.warn("PMTiles spec version 2 has been | |
deprecated; please see github.com/protomaps/PMTiles for tools to | |
upgrade"),2):e.getUint16(2,!0)===1?(console.warn("PMTiles spec version | |
1 has been deprecated; please see github.com/protomaps/PMTiles for | |
tools to upgrade"),1):3}var E=class extends Error{};async function | |
Ze(t,e){let r=await t.getBytes(0,16384);if(new | |
DataView(r.data).getUint16(0,!0)!==19792)throw new Error("Wrong magic | |
number for PMTiles archive");if(Ft(r.data)<3)return[await | |
ae.getHeader(t)];let | |
i=r.data.slice(0,Zt),a=kt(i,r.etag),o=r.data.slice(a.rootDirectoryOffset,a.rootDirectoryOffset+a.rootDirectoryLength),s=`${t.getKey()}|${a.etag||""}|${a.rootDirectoryOffset}|${a.rootDirectoryLength}`,l=Ie(await | |
e(o,a.internalCompression));return[a,[s,l.length,l]]}async function | |
Oe(t,e,r,n,i){let a=await t.getBytes(r,n,void 0,i.etag),o=await | |
e(a.data,i.internalCompression),s=Ie(o);if(s.length===0)throw new | |
Error("Empty directory is invalid");return s}var | |
N=class{constructor(e=100,r=!0,n=fe){this.cache=new | |
Map,this.maxCacheEntries=e,this.counter=1,this.decompress=n}async | |
getHeader(e){let r=e.getKey(),n=this.cache.get(r);if(n)return | |
n.lastUsed=this.counter++,n.data;let i=await | |
Ze(e,this.decompress);return | |
i[1]&&this.cache.set(i[1][0],{lastUsed:this.counter++,data:i[1][2]}),this.cache.set(r,{lastUsed:this.counter++,data:i[0]}),this.prune(),i[0]}async | |
getDirectory(e,r,n,i){let | |
a=`${e.getKey()}|${i.etag||""}|${r}|${n}`,o=this.cache.get(a);if(o)return | |
o.lastUsed=this.counter++,o.data;let s=await | |
Oe(e,this.decompress,r,n,i);return | |
this.cache.set(a,{lastUsed:this.counter++,data:s}),this.prune(),s}async | |
getArrayBuffer(e,r,n,i){let | |
a=`${e.getKey()}|${i.etag||""}|${r}|${n}`,o=this.cache.get(a);if(o)return | |
o.lastUsed=this.counter++,await o.data;let s=await e.getBytes(r,n,void | |
0,i.etag);return | |
this.cache.set(a,{lastUsed:this.counter++,data:s.data}),this.prune(),s.data}prune(){if(this.cache.size>this.maxCacheEntries){let | |
e=1/0,r;this.cache.forEach((n,i)=>{n.lastUsed<e&&(e=n.lastUsed,r=i)}),r&&this.cache.delete(r)}}async | |
invalidate(e){this.cache.delete(e.getKey())}},ue=class{constructor(e=100,r=!0,n=fe){this.cache=new | |
Map,this.invalidations=new | |
Map,this.maxCacheEntries=e,this.counter=1,this.decompress=n}async | |
getHeader(e){let r=e.getKey(),n=this.cache.get(r);if(n)return | |
n.lastUsed=this.counter++,await n.data;let i=new | |
Promise((a,o)=>{Ze(e,this.decompress).then(s=>{s[1]&&this.cache.set(s[1][0],{lastUsed:this.counter++,data:Promise.resolve(s[1][2])}),a(s[0]),this.prune()}).catch(s=>{o(s)})});return | |
this.cache.set(r,{lastUsed:this.counter++,data:i}),i}async | |
getDirectory(e,r,n,i){let | |
a=`${e.getKey()}|${i.etag||""}|${r}|${n}`,o=this.cache.get(a);if(o)return | |
o.lastUsed=this.counter++,await o.data;let s=new | |
Promise((l,c)=>{Oe(e,this.decompress,r,n,i).then(u=>{l(u),this.prune()}).catch(u=>{c(u)})});return | |
this.cache.set(a,{lastUsed:this.counter++,data:s}),s}async | |
getArrayBuffer(e,r,n,i){let | |
a=`${e.getKey()}|${i.etag||""}|${r}|${n}`,o=this.cache.get(a);if(o)return | |
o.lastUsed=this.counter++,await o.data;let s=new | |
Promise((l,c)=>{e.getBytes(r,n,void | |
0,i.etag).then(u=>{l(u.data),this.cache.has(a),this.prune()}).catch(u=>{c(u)})});return | |
this.cache.set(a,{lastUsed:this.counter++,data:s}),s}prune(){if(this.cache.size>=this.maxCacheEntries){let | |
e=1/0,r;this.cache.forEach((n,i)=>{n.lastUsed<e&&(e=n.lastUsed,r=i)}),r&&this.cache.delete(r)}}async | |
invalidate(e){let r=e.getKey();if(this.invalidations.get(r))return | |
await this.invalidations.get(r);this.cache.delete(e.getKey());let | |
n=new | |
Promise((i,a)=>{this.getHeader(e).then(o=>{i(),this.invalidations.delete(r)}).catch(o=>{a(o)})});this.invalidations.set(r,n)}},O=class{constructor(e,r,n){typeof | |
e=="string"?this.source=new | |
se(e):this.source=e,n?this.decompress=n:this.decompress=fe,r?this.cache=r:this.cache=new | |
ue}async getHeader(){return await | |
this.cache.getHeader(this.source)}async getZxyAttempt(e,r,n,i){let | |
a=It(e,r,n),o=await | |
this.cache.getHeader(this.source);if(o.specVersion<3)return | |
ae.getZxy(o,this.source,this.cache,e,r,n,i);if(e<o.minZoom||e>o.maxZoom)return;let | |
s=o.rootDirectoryOffset,l=o.rootDirectoryLength;for(let | |
c=0;c<=3;c++){let u=await | |
this.cache.getDirectory(this.source,s,l,o),f=Ot(u,a);if(f){if(f.runLength>0){let | |
h=await | |
this.source.getBytes(o.tileDataOffset+f.offset,f.length,i,o.etag);return{data:await | |
this.decompress(h.data,o.tileCompression),cacheControl:h.cacheControl,expires:h.expires}}s=o.leafDirectoryOffset+f.offset,l=f.length}else | |
return}throw Error("Maximum directory depth exceeded")}async | |
getZxy(e,r,n,i){try{return await | |
this.getZxyAttempt(e,r,n,i)}catch(a){if(a instanceof E)return | |
this.cache.invalidate(this.source),await | |
this.getZxyAttempt(e,r,n,i);throw a}}async getMetadataAttempt(){let | |
e=await this.cache.getHeader(this.source),r=await | |
this.source.getBytes(e.jsonMetadataOffset,e.jsonMetadataLength,void | |
0,e.etag),n=await this.decompress(r.data,e.internalCompression),i=new | |
TextDecoder("utf-8");return JSON.parse(i.decode(n))}async | |
getMetadata(){try{return await this.getMetadataAttempt()}catch(e){if(e | |
instanceof E)return this.cache.invalidate(this.source),await | |
this.getMetadataAttempt();throw e}}};var | |
ke=(t,e)=>e?e.replaceAll("{name}",t):t+".pmtiles",$t=/^\/(?<NAME>[0-9a-zA-Z\/!\-_\.\*\'\(\)]+)\/(?<Z>\d+)\/(?<X>\d+)\/(?<Y>\d+).(?<EXT>[a-z]+)$/,Gt=/^\/(?<NAME>[0-9a-zA-Z\/!\-_\.\*\'\(\)]+).json$/,Fe=t=>{let | |
e=t.match($t);if(e){let | |
n=e.groups;return{ok:!0,name:n.NAME,tile:[+n.Z,+n.X,+n.Y],ext:n.EXT}}let | |
r=t.match(Gt);return | |
r?{ok:!0,name:r.groups.NAME,ext:"json"}:{ok:!1,name:"",tile:[0,0,0],ext:""}},$e=(t,e,r,n)=>{let | |
i="";return | |
t.tileType===1?i=".mvt":t.tileType===2?i=".png":t.tileType===3?i=".jpg":t.tileType===4?i=".webp":t.tileType===5&&(i=".avif"),{tilejson:"3.0.0",scheme:"xyz",tiles:["https://"+r+"/"+n+"/{z}/{x}/{y}"+i],vector_layers:e.vector_layers,attribution:e.attribution,description:e.description,name:e.name,version:e.version,bounds:[t.minLon,t.minLat,t.maxLon,t.maxLat],center:[t.centerLon,t.centerLat,t.centerZoom],minzoom:t.minZoom,maxzoom:t.maxZoom}};var | |
Ge=require("crypto"),le=it(require("zlib")),j=require("@aws-sdk/client-s3"),Ke=require("@aws-sdk/node-http-handler"),Kt=new | |
j.S3Client({requestHandler:new | |
Ke.NodeHttpHandler({connectionTimeout:500,socketTimeout:500})});async | |
function Ne(t,e){if(e===1||e===0)return t;if(e===2)return | |
le.default.gunzipSync(t);throw Error("Compression method not | |
supported")}var Nt=new N(void 0,void | |
0,Ne),ce=class{constructor(e){this.archiveName=e}getKey(){return | |
this.archiveName}async getBytes(e,r,n,i){let a;try{a=await Kt.send(new | |
j.GetObjectCommand({Bucket:process.env.BUCKET,Key:ke(this.archiveName,process.env.PMTILES_PATH),Range:"bytes="+e+"-"+(e+r-1),IfMatch:i}))}catch(s){throw | |
s instanceof Error&&s.name==="PreconditionFailed"?new E:s}let o=await | |
a.Body?.transformToByteArray();if(!o)throw Error("Failed to read S3 | |
response | |
body");return{data:o.buffer,etag:a.ETag,expires:a.Expires?.toISOString(),cacheControl:a.CacheControl}}},z=(t,e,r=!1,n={})=>({statusCode:t,body:e,headers:n,isBase64Encoded:r}),je=async(t,e,r)=>{let | |
n,i=!1;if(t.pathParameters)if(i=!0,t.pathParameters.proxy)n=`/${t.pathParameters.proxy}`;else | |
return z(500,"Proxy integration missing tile_path parameter");else | |
n=t.rawPath;if(!n)return z(500,"Invalid event configuration");let | |
a={};process.env.CORS&&(a["Access-Control-Allow-Origin"]=process.env.CORS);let{ok:o,name:s,tile:l,ext:c}=Fe(n);if(!o)return | |
z(400,"Invalid tile URL",!1,a);let u=new ce(s),f=new | |
O(u,Nt,Ne);try{let h=await | |
f.getHeader();if(!l){a["Content-Type"]="application/json";let | |
p=$e(h,await | |
f.getMetadata(),process.env.PUBLIC_HOSTNAME||t.headers["x-distribution-domain-name"]||"",s);return | |
z(200,JSON.stringify(p),!1,a)}if(l[0]<h.minZoom||l[0]>h.maxZoom)return | |
z(404,"",!1,a);for(let p | |
of[[1,"mvt"],[2,"png"],[3,"jpg"],[4,"webp"],[5,"avif"]])if(h.tileType===p[0]&&c!==p[1]){if(h.tileType===1&&c==="pbf")continue;return | |
z(400,`Bad request: requested .${c} but archive has type | |
.${p[1]}`,!1,a)}let g=await | |
f.getZxy(l[0],l[1],l[2]);if(g){switch(h.tileType){case | |
1:a["Content-Type"]="application/vnd.mapbox-vector-tile";break;case | |
2:a["Content-Type"]="image/png";break;case | |
3:a["Content-Type"]="image/jpeg";break;case | |
4:a["Content-Type"]="image/webp";break;case | |
5:a["Content-Type"]="image/avif";break}let | |
p=g.data;if(r&&(p=r(p,h.tileType)),a["Cache-Control"]=process.env.CACHE_CONTROL||"public, | |
max-age=86400",a.ETag=`"${(0,Ge.createHash)("sha256").update(Buffer.from(p)).digest("hex")}"`,i){let | |
y=le.default.gzipSync(p);return | |
a["Content-Encoding"]="gzip",z(200,Buffer.from(y).toString("base64"),!0,a)}return | |
z(200,Buffer.from(p).toString("base64"),!0,a)}return | |
z(204,"",!1,a)}catch(h){if(h.name==="AccessDenied")return | |
z(403,"Bucket access unauthorized",!1,a);throw | |
h}},jt=async(t,e)=>await | |
je(t,e);0&&(module.exports={handler,handlerRaw}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment