Skip to content

Instantly share code, notes, and snippets.

@bdon
Created August 28, 2024 04:25
Show Gist options
  • Save bdon/ce3180498561fb0904b7d7ac6df441de to your computer and use it in GitHub Desktop.
Save bdon/ce3180498561fb0904b7d7ac6df441de to your computer and use it in GitHub Desktop.
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